/*
 ===========================================================================
 Copyright (C) 1999-2005 Id Software, Inc.
 Copyright (C) 2000-2006 Tim Angus

 This file is part of Tremulous.

 Tremulous is free software; you can redistribute it
 and/or modify it under the terms of the GNU General Public License as
 published by the Free Software Foundation; either version 2 of the License,
 or (at your option) any later version.

 Tremulous is distributed in the hope that it will be
 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with Tremulous; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 ===========================================================================
 */

#include "g_local.h"
#include "acebot.h"

#define QVM_NAME       "Zombie QVM"
#define QVM_VERSIONNUM      "1.0"

level_locals_t level;

typedef struct
{
  vmCvar_t *vmCvar;
  char *cvarName;
  char *defaultString;
  int cvarFlags;
  int modificationCount; // for tracking changes
  qboolean trackChange; // track this variable, and announce if changed
  qboolean teamShader; // track and if changed, update shader state
} cvarTable_t;

gentity_t g_entities[MAX_GENTITIES];
gclient_t g_clients[MAX_CLIENTS];

vmCvar_t g_fraglimit;
vmCvar_t g_timelimit;
vmCvar_t g_suddenDeathTime;
vmCvar_t g_suddenDeath;
vmCvar_t g_suddenDeathMode;
vmCvar_t g_capturelimit;
vmCvar_t g_friendlyFire;
vmCvar_t g_friendlyFireAliens;
vmCvar_t g_friendlyFireHumans;
vmCvar_t g_friendlyFireMovementAttacks;
vmCvar_t g_retribution;
vmCvar_t g_friendlyBuildableFire;
vmCvar_t g_password;
vmCvar_t g_needpass;
vmCvar_t g_maxclients;
vmCvar_t g_maxGameClients;
vmCvar_t g_dedicated;
vmCvar_t g_speed;
vmCvar_t g_gravity;
vmCvar_t g_cheats;
vmCvar_t g_knockback;
vmCvar_t g_quadfactor;
vmCvar_t g_inactivity;
vmCvar_t g_debugMove;
vmCvar_t g_debugDamage;
vmCvar_t g_debugAlloc;
vmCvar_t g_weaponRespawn;
vmCvar_t g_weaponTeamRespawn;
vmCvar_t g_motd;
vmCvar_t g_synchronousClients;
vmCvar_t g_warmup;
vmCvar_t g_warmupMode;
vmCvar_t g_doWarmup;
vmCvar_t g_restarted;
vmCvar_t g_lockTeamsAtStart;
vmCvar_t g_logFile;
vmCvar_t g_logFileSync;
vmCvar_t g_blood;
vmCvar_t g_podiumDist;
vmCvar_t g_podiumDrop;
vmCvar_t g_allowVote;
vmCvar_t g_requireVoteReasons;
vmCvar_t g_voteLimit;
vmCvar_t g_suddenDeathVotePercent;
vmCvar_t g_suddenDeathVoteDelay;
vmCvar_t g_mapVotesPercent;
vmCvar_t g_designateVotes;
vmCvar_t g_teamAutoJoin;
vmCvar_t g_teamForceBalance;
vmCvar_t g_banIPs;
vmCvar_t g_filterBan;
vmCvar_t g_smoothClients;
vmCvar_t g_clientUpgradeNotice;
vmCvar_t pmove_fixed;
vmCvar_t pmove_msec;
vmCvar_t g_rankings;
vmCvar_t g_listEntity;
vmCvar_t g_minCommandPeriod;
vmCvar_t g_minNameChangePeriod;
vmCvar_t g_maxNameChanges;
vmCvar_t g_newbieNumbering;
vmCvar_t g_newbieNamePrefix;

vmCvar_t g_humanBuildPoints;
vmCvar_t g_alienBuildPoints;
vmCvar_t g_humanStage;
vmCvar_t g_humanKills;
vmCvar_t g_humanMaxStage;
vmCvar_t g_humanStage2Threshold;
vmCvar_t g_humanStage3Threshold;
vmCvar_t g_alienStage;
vmCvar_t g_alienKills;
vmCvar_t g_alienMaxStage;
vmCvar_t g_alienStage2Threshold;
vmCvar_t g_alienStage3Threshold;
vmCvar_t g_teamImbalanceWarnings;

vmCvar_t g_unlagged;

vmCvar_t g_disabledEquipment;
vmCvar_t g_disabledClasses;
vmCvar_t g_disabledBuildables;

vmCvar_t g_markDeconstruct;
vmCvar_t g_deconDead;

vmCvar_t g_debugMapRotation;
vmCvar_t g_currentMapRotation;
vmCvar_t g_currentMap;
vmCvar_t g_nextMap;
vmCvar_t g_initialMapRotation;

vmCvar_t g_shove;

vmCvar_t g_mapConfigs;
vmCvar_t g_chatTeamPrefix;
vmCvar_t g_actionPrefix;
vmCvar_t g_floodMaxDemerits;
vmCvar_t g_floodMinTime;

vmCvar_t g_layouts;
vmCvar_t g_layoutAuto;
vmCvar_t g_layoutmaking;

vmCvar_t g_admin;
vmCvar_t g_adminLog;
vmCvar_t g_adminParseSay;
vmCvar_t g_adminSayFilter;
vmCvar_t g_adminNameProtect;
vmCvar_t g_adminTempBan;
vmCvar_t g_adminMaxBan;
vmCvar_t g_adminMapLog;
vmCvar_t g_minLevelToJoinTeam;
vmCvar_t g_forceAutoSelect;

vmCvar_t g_privateMessages;
vmCvar_t g_decolourLogfiles;
vmCvar_t g_minLevelToSpecMM1;
vmCvar_t g_publicSayadmins;
vmCvar_t g_myStats;
vmCvar_t g_antiSpawnBlock;
vmCvar_t g_banNotice;

vmCvar_t g_devmapKillerHP;

vmCvar_t g_buildLogMaxLength;

vmCvar_t g_tag;

vmCvar_t g_dretchPunt;

vmCvar_t g_allowShare;

vmCvar_t g_devmapNoGod;
vmCvar_t g_devmapNoStructDmg;

vmCvar_t g_voteMinTime;
vmCvar_t g_mapvoteMaxTime;

vmCvar_t g_msg;
vmCvar_t g_msgTime;

vmCvar_t g_ctn;
vmCvar_t g_ctnbuildlimit;
vmCvar_t g_ctncapturetime;

//Survival
vmCvar_t g_survival;

//ROTAX
vmCvar_t g_ambush;
vmCvar_t g_ambush_granger_s1;
vmCvar_t g_ambush_dretch_s2;
vmCvar_t g_ambush_basilisk_s3;
vmCvar_t g_ambush_basilisk2_s4;
vmCvar_t g_ambush_marauder_s5;
vmCvar_t g_ambush_marauder2_s6;
vmCvar_t g_ambush_dragon_s7;
vmCvar_t g_ambush_dragon2_s8;
vmCvar_t g_ambush_tyrants_to_win;
vmCvar_t g_ambush_dodge;
vmCvar_t g_ambush_dodge_random;
vmCvar_t g_ambush_rebuild_time;
vmCvar_t g_ambush_sec_to_start;
vmCvar_t g_ambush_stage_suicide;
vmCvar_t g_ambush_no_egg_ffoff;
vmCvar_t g_ambush_kill_spawns;
vmCvar_t g_ambush_att_buildables;
vmCvar_t g_ambush_range;
vmCvar_t g_ambush_turnangle;

vmCvar_t g_bot;

vmCvar_t g_bot_mgun;
vmCvar_t g_bot_shotgun;
vmCvar_t g_bot_psaw;
vmCvar_t g_bot_lasgun;
vmCvar_t g_bot_mdriver;
vmCvar_t g_bot_chaingun;
vmCvar_t g_bot_prifle;
vmCvar_t g_bot_flamer;
vmCvar_t g_bot_lcannon;

int ROTACAK_ambush_rebuild_time_temp = 0;
int ROTACAK_ambush_stage = 1;
int ROTACAK_ambush_kills = 0;
int mega_wave = 1;

vmCvar_t g_antispawncamp;

#if defined(ACEBOT)
vmCvar_t ace_debug;
vmCvar_t ace_showNodes;
vmCvar_t ace_showLinks;
vmCvar_t ace_showPath;
vmCvar_t ace_pickLongRangeGoal;
vmCvar_t ace_pickShortRangeGoal;
vmCvar_t ace_attackEnemies;
vmCvar_t ace_spSkill;
vmCvar_t ace_botsFile;
#endif
vmCvar_t director_debug;

static cvarTable_t gameCvarTable[] =
    {
    // don't override the cheat state set by the system
        { &g_cheats, "sv_cheats", "", 0, 0, qfalse },

        // noset vars
        { NULL, "gamename", GAME_VERSION, CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
        { NULL, "gamedate", __DATE__, CVAR_ROM, 0, qfalse },
        { &g_restarted, "g_restarted", "0", CVAR_ROM, 0, qfalse },
        { &g_lockTeamsAtStart, "g_lockTeamsAtStart", "0", CVAR_ROM, 0, qfalse },
        { NULL, "sv_mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
        { NULL, "P", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
        { NULL, "ff", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
        { NULL, "qvm_version", QVM_NAME " " QVM_VERSIONNUM " (" __DATE__ ", " __TIME__ ")",
            CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },

        // latched vars

        { &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0,
            qfalse },
        { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_ARCHIVE
            | CVAR_NORESTART, 0, qtrue },

        // change anytime vars
        { &g_timelimit, "timelimit", "45", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0,
            qtrue },
        { &g_suddenDeathTime, "g_suddenDeathTime", "30", CVAR_SERVERINFO | CVAR_ARCHIVE
            | CVAR_NORESTART, 0, qtrue },
        { &g_suddenDeathMode, "g_suddenDeathMode", "0", CVAR_SERVERINFO | CVAR_ARCHIVE
            | CVAR_NORESTART, 0, qtrue },
        { &g_suddenDeath, "g_suddenDeath", "0", CVAR_SERVERINFO | CVAR_NORESTART, 0, qtrue },

        { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse },

        { &g_friendlyFire, "g_friendlyFire", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue },
        { &g_friendlyFireAliens, "g_friendlyFireAliens", "0", CVAR_ARCHIVE, 0, qtrue },
        { &g_friendlyFireHumans, "g_friendlyFireHumans", "0", CVAR_ARCHIVE, 0, qtrue },
        { &g_retribution, "g_retribution", "0", CVAR_ARCHIVE, 0, qtrue },
        { &g_friendlyBuildableFire, "g_friendlyBuildableFire", "0", CVAR_ARCHIVE | CVAR_SERVERINFO,
            0, qtrue },
        { &g_friendlyFireMovementAttacks, "g_friendlyFireMovementAttacks", "1", CVAR_ARCHIVE, 0,
            qtrue },
        { &g_devmapNoGod, "g_devmapNoGod", "0", CVAR_ARCHIVE, 0, qtrue },
        { &g_devmapNoStructDmg, "g_devmapNoStructDmg", "0", CVAR_ARCHIVE, 0, qtrue },

        { &g_teamAutoJoin, "g_teamAutoJoin", "0", CVAR_ARCHIVE },
        { &g_teamForceBalance, "g_teamForceBalance", "1", CVAR_ARCHIVE },

        { &g_warmup, "g_warmup", "10", CVAR_ARCHIVE, 0, qtrue },
        { &g_warmupMode, "g_warmupMode", "1", CVAR_ARCHIVE, 0, qtrue },
        { &g_doWarmup, "g_doWarmup", "1", CVAR_ARCHIVE, 0, qtrue },
        { &g_logFile, "g_logFile", "games.log", CVAR_ARCHIVE, 0, qfalse },
        { &g_logFileSync, "g_logFileSync", "0", CVAR_ARCHIVE, 0, qfalse },

        { &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse },

        { &g_banIPs, "g_banIPs", "", CVAR_ARCHIVE, 0, qfalse },
        { &g_filterBan, "g_filterBan", "1", CVAR_ARCHIVE, 0, qfalse },

        { &g_needpass, "g_needpass", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },

        { &g_dedicated, "dedicated", "0", 0, 0, qfalse },

        { &g_speed, "g_speed", "320", CVAR_SERVERINFO, 0, qtrue },
        { &g_gravity, "g_gravity", "800", CVAR_SERVERINFO, 0, qtrue },
        { &g_knockback, "g_knockback", "1000", CVAR_SERVERINFO, 0, qtrue },
        { &g_quadfactor, "g_quadfactor", "3", 0, 0, qtrue },
        { &g_weaponRespawn, "g_weaponrespawn", "5", 0, 0, qtrue },
        { &g_weaponTeamRespawn, "g_weaponTeamRespawn", "30", 0, 0, qtrue },
        { &g_inactivity, "g_inactivity", "0", 0, 0, qtrue },
        { &g_debugMove, "g_debugMove", "0", 0, 0, qfalse },
        { &g_debugDamage, "g_debugDamage", "0", 0, 0, qfalse },
        { &g_debugAlloc, "g_debugAlloc", "0", 0, 0, qfalse },
        { &g_motd, "g_motd", "", 0, 0, qfalse },
        { &g_blood, "com_blood", "1", 0, 0, qfalse },

        { &g_podiumDist, "g_podiumDist", "80", 0, 0, qfalse },
        { &g_podiumDrop, "g_podiumDrop", "70", 0, 0, qfalse },

        { &g_allowVote, "g_allowVote", "1", CVAR_ARCHIVE, 0, qfalse },
        { &g_requireVoteReasons, "g_requireVoteReasons", "0", CVAR_ARCHIVE, 0, qfalse },
        { &g_voteLimit, "g_voteLimit", "5", CVAR_ARCHIVE, 0, qfalse },
        { &g_voteMinTime, "g_voteMinTime", "120", CVAR_ARCHIVE, 0, qfalse },
        { &g_mapvoteMaxTime, "g_mapvoteMaxTime", "240", CVAR_ARCHIVE, 0, qfalse },
        { &g_suddenDeathVotePercent, "g_suddenDeathVotePercent", "74", CVAR_ARCHIVE, 0, qfalse },
        { &g_suddenDeathVoteDelay, "g_suddenDeathVoteDelay", "180", CVAR_ARCHIVE, 0, qfalse },
        { &g_mapVotesPercent, "g_mapVotesPercent", "50", CVAR_ARCHIVE, 0, qfalse },
        { &g_designateVotes, "g_designateVotes", "0", CVAR_ARCHIVE, 0, qfalse },

        { &g_listEntity, "g_listEntity", "0", 0, 0, qfalse },
        { &g_minCommandPeriod, "g_minCommandPeriod", "500", 0, 0, qfalse },
        { &g_minNameChangePeriod, "g_minNameChangePeriod", "5", 0, 0, qfalse },
        { &g_maxNameChanges, "g_maxNameChanges", "5", 0, 0, qfalse },
        { &g_newbieNumbering, "g_newbieNumbering", "0", CVAR_ARCHIVE, 0, qfalse },
        { &g_newbieNamePrefix, "g_newbieNamePrefix", "Newbie#", CVAR_ARCHIVE, 0, qfalse },

        { &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse },
        { &g_clientUpgradeNotice, "g_clientUpgradeNotice", "1", 0, 0, qfalse },
        { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse },
        { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse },

        { &g_humanBuildPoints, "g_humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, CVAR_SERVERINFO, 0,
            qfalse },
        { &g_alienBuildPoints, "g_alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, CVAR_SERVERINFO, 0,
            qfalse },
        { &g_humanStage, "g_humanStage", "0", 0, 0, qfalse },
        { &g_humanKills, "g_humanKills", "0", 0, 0, qfalse },
        { &g_humanMaxStage, "g_humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, 0, 0, qfalse },
        { &g_humanStage2Threshold, "g_humanStage2Threshold", DEFAULT_HUMAN_STAGE2_THRESH, 0, 0,
            qfalse },
        { &g_humanStage3Threshold, "g_humanStage3Threshold", DEFAULT_HUMAN_STAGE3_THRESH, 0, 0,
            qfalse },
        { &g_alienStage, "g_alienStage", "0", 0, 0, qfalse },
        { &g_alienKills, "g_alienKills", "0", 0, 0, qfalse },
        { &g_alienMaxStage, "g_alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, 0, 0, qfalse },
        { &g_alienStage2Threshold, "g_alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, 0, 0,
            qfalse },
        { &g_alienStage3Threshold, "g_alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, 0, 0,
            qfalse },

        { &g_teamImbalanceWarnings, "g_teamImbalanceWarnings", "30", CVAR_ARCHIVE, 0, qfalse },

        { &g_unlagged, "g_unlagged", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse },

        { &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM, 0, qfalse },
        { &g_disabledClasses, "g_disabledClasses", "", CVAR_ROM, 0, qfalse },
        { &g_disabledBuildables, "g_disabledBuildables", "", CVAR_ROM, 0, qfalse },

        { &g_chatTeamPrefix, "g_chatTeamPrefix", "1", CVAR_ARCHIVE },
        { &g_actionPrefix, "g_actionPrefix", "* ", CVAR_ARCHIVE, 0, qfalse },
        { &g_floodMaxDemerits, "g_floodMaxDemerits", "5000", CVAR_ARCHIVE, 0, qfalse },
        { &g_floodMinTime, "g_floodMinTime", "2000", CVAR_ARCHIVE, 0, qfalse },

        { &g_markDeconstruct, "g_markDeconstruct", "0", CVAR_ARCHIVE, 0, qfalse },
        { &g_deconDead, "g_deconDead", "0", CVAR_ARCHIVE, 0, qtrue },

        { &g_debugMapRotation, "g_debugMapRotation", "0", 0, 0, qfalse },
        { &g_currentMapRotation, "g_currentMapRotation", "-1", 0, 0, qfalse }, // -1 = NOT_ROTATING
        { &g_currentMap, "g_currentMap", "0", 0, 0, qfalse },
        { &g_nextMap, "g_nextMap", "", 0, 0, qtrue },
        { &g_initialMapRotation, "g_initialMapRotation", "", CVAR_ARCHIVE, 0, qfalse },
        { &g_shove, "g_shove", "15", CVAR_ARCHIVE, 0, qfalse },
        { &g_layoutmaking, "g_layoutmaking", "0", CVAR_ARCHIVE, 0, qfalse },
        { &g_mapConfigs, "g_mapConfigs", "", CVAR_ARCHIVE, 0, qfalse },
        { NULL, "g_mapConfigsLoaded", "0", CVAR_ROM, 0, qfalse },

        { &g_layouts, "g_layouts", "", CVAR_LATCH, 0, qfalse },
        { &g_layoutAuto, "g_layoutAuto", "1", CVAR_ARCHIVE, 0, qfalse },

        { &g_admin, "g_admin", "admin.dat", CVAR_ARCHIVE, 0, qfalse },
        { &g_adminLog, "g_adminLog", "admin.log", CVAR_ARCHIVE, 0, qfalse },
        { &g_adminParseSay, "g_adminParseSay", "1", CVAR_ARCHIVE, 0, qfalse },
        { &g_adminSayFilter, "g_adminSayFilter", "0", CVAR_ARCHIVE, 0, qfalse },
        { &g_adminNameProtect, "g_adminNameProtect", "1", CVAR_ARCHIVE, 0, qfalse },
        { &g_adminTempBan, "g_adminTempBan", "2m", CVAR_ARCHIVE, 0, qfalse },
        { &g_adminMaxBan, "g_adminMaxBan", "2w", CVAR_ARCHIVE, 0, qfalse },
        { &g_adminMapLog, "g_adminMapLog", "", CVAR_ROM, 0, qfalse },
        { &g_minLevelToJoinTeam, "g_minLevelToJoinTeam", "0", CVAR_ARCHIVE, 0, qfalse },
        { &g_forceAutoSelect, "g_forceAutoSelect", "0", CVAR_ARCHIVE, 0, qtrue },

        { &g_privateMessages, "g_privateMessages", "1", CVAR_ARCHIVE, 0, qfalse },
        { &g_decolourLogfiles, "g_decolourLogfiles", "0", CVAR_ARCHIVE, 0, qfalse },
        { &g_buildLogMaxLength, "g_buildLogMaxLength", "50", CVAR_ARCHIVE, 0, qfalse },
        { &g_myStats, "g_myStats", "1", CVAR_ARCHIVE, 0, qtrue },
        { &g_publicSayadmins, "g_publicSayadmins", "1", CVAR_ARCHIVE, 0, qfalse },
        { &g_minLevelToSpecMM1, "g_minLevelToSpecMM1", "0", CVAR_ARCHIVE, 0, qfalse },
        { &g_antiSpawnBlock, "g_antiSpawnBlock", "0", CVAR_ARCHIVE, 0, qfalse },

        { &g_devmapKillerHP, "g_devmapKillerHP", "0", CVAR_ARCHIVE, 0, qtrue },

        { &g_tag, "g_tag", "main", CVAR_INIT, 0, qfalse },

        { &g_dretchPunt, "g_dretchPunt", "1", CVAR_ARCHIVE, 0, qfalse },

        { &g_msg, "g_msg", "", CVAR_ARCHIVE, 0, qfalse },
        { &g_msgTime, "g_msgTime", "0", CVAR_ARCHIVE, 0, qfalse },

        { &g_rankings, "g_rankings", "0", 0, 0, qfalse },
        { &g_allowShare, "g_allowShare", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse },
        { &g_banNotice, "g_banNotice", "", CVAR_ARCHIVE, 0, qfalse },
        { &g_ctn, "g_ctn", "1", CVAR_ARCHIVE, 0, qfalse },

        { &g_survival, "g_survival", "0", CVAR_ARCHIVE | CVAR_NORESTART, 0, qfalse },

        { &g_ctnbuildlimit, "g_ctnbuildlimit", "5", CVAR_ARCHIVE, 0, qfalse },
        { &g_antispawncamp, "g_antispawncamp", "5000", CVAR_ARCHIVE, 0, qfalse },
        { &g_ctncapturetime, "g_ctncapturetime", "10000", CVAR_ARCHIVE, 0, qfalse },

#if defined(ACEBOT)
        { &ace_debug, "ace_debug", "0", 0, 0, qfalse },
        { &ace_showNodes, "ace_showNodes", "0", 0, 0, qfalse },
        { &ace_showLinks, "ace_showLinks", "0", 0, 0, qfalse },
        { &ace_showPath, "ace_showPath", "0", 0, 0, qfalse },
        { &ace_pickLongRangeGoal, "ace_pickLongRangeGoal", "1", 0, 0, qfalse },
        { &ace_pickShortRangeGoal, "ace_pickShortRangeGoal", "1", 0, 0, qfalse },
        { &ace_attackEnemies, "ace_attackEnemies", "1", 0, 0, qfalse },
        { &ace_spSkill, "g_spSkill", "3", 0, 0, qfalse }, // FIXME rename
        { &ace_botsFile, "g_botsFile", "3", 0, 0, qfalse }, // FIXME rename
#endif
        { &director_debug, "director_debug", "0", 0, 0, qfalse },

    };

static int gameCvarTableSize = sizeof(gameCvarTable) / sizeof(gameCvarTable[0]);

void
G_InitGame(int levelTime, int randomSeed, int restart);
void
G_RunFrame(int levelTime);
void
G_ShutdownGame(int restart);
void
CheckExitRules(void);

void
G_CountSpawns(void);
void
G_CalculateBuildPoints(void);

void
G_UpdateCamper(void);
void
G_addBot(void);
void
G_Director(void);

/*
 ================
 vmMain

 This is the only way control passes into the module.
 This must be the very first function compiled into the .q3vm file
 ================
 */
intptr_t
vmMain(int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7,
  int arg8, int arg9, int arg10, int arg11)
{
  switch(command)
  {
    case GAME_INIT:
      G_InitGame(arg0, arg1, arg2);
      return 0;

    case GAME_SHUTDOWN:
      G_ShutdownGame(arg0);
      return 0;

    case GAME_CLIENT_CONNECT:
      return (intptr_t) ClientConnect(arg0, arg1);

    case GAME_CLIENT_THINK:
      g_comboClear();
      ClientThink(arg0);
      g_comboPrint();
      return 0;

    case GAME_CLIENT_USERINFO_CHANGED:
      ClientUserinfoChanged(arg0);
      return 0;

    case GAME_CLIENT_DISCONNECT:
      ClientDisconnect(arg0);
      return 0;

    case GAME_CLIENT_BEGIN:
      ClientBegin(arg0);
      return 0;

    case GAME_CLIENT_COMMAND:
      ClientCommand(arg0);
      return 0;

    case GAME_RUN_FRAME:
      g_comboClear();
      G_RunFrame(arg0);
      g_comboPrint();
      return 0;

    case GAME_CONSOLE_COMMAND:
      return ConsoleCommand();
  }

  return -1;
}

void QDECL
G_Printf(const char *fmt, ...)
{
  va_list argptr;
  char text[1024];

  va_start(argptr, fmt);
  vsprintf(text, fmt, argptr);
  va_end(argptr);

  trap_Printf(text);
}

void QDECL
G_Error(const char *fmt, ...)
{
  va_list argptr;
  char text[1024];

  va_start(argptr, fmt);
  vsprintf(text, fmt, argptr);
  va_end(argptr);

  trap_Error(text);
}

/*
 ================
 G_FindTeams

 Chain together all entities with a matching team field.
 Entity teams are used for item groups and multi-entity mover groups.

 All but the first will have the FL_TEAMSLAVE flag set and teammaster field set
 All but the last will have the teamchain field set to the next one
 ================
 */
void
G_FindTeams(void)
{
  gentity_t *e, *e2;
  int i, j;
  int c, c2;

  c = 0;
  c2 = 0;

  for(i = 1, e = g_entities + i;i < level.num_entities;i++, e++)
  {
    if (!e->inuse)
      continue;

    if (!e->team)
      continue;

    if (e->flags & FL_TEAMSLAVE)
      continue;

    e->teammaster = e;
    c++;
    c2++;

    for(j = i + 1, e2 = e + 1;j < level.num_entities;j++, e2++)
    {
      if (!e2->inuse)
        continue;

      if (!e2->team)
        continue;

      if (e2->flags & FL_TEAMSLAVE)
        continue;

      if (!strcmp(e->team, e2->team))
      {
        c2++;
        e2->teamchain = e->teamchain;
        e->teamchain = e2;
        e2->teammaster = e;
        e2->flags |= FL_TEAMSLAVE;

        // make sure that targets only point at the master
        if (e2->targetname)
        {
          e->targetname = e2->targetname;
          e2->targetname = NULL;
        }
      }
    }
  }

  G_Printf("%i teams with %i entities\n", c, c2);
}

void
G_RemapTeamShaders(void)
{
}

/*
 =================
 G_RegisterCvars
 =================
 */
void
G_RegisterCvars(void)
{
  int i;
  cvarTable_t *cv;
  qboolean remapped = qfalse;

  for(i = 0, cv = gameCvarTable;i < gameCvarTableSize;i++, cv++)
  {
    trap_Cvar_Register(cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags);

    if (cv->vmCvar)
      cv->modificationCount = cv->vmCvar->modificationCount;

    if (cv->teamShader)
      remapped = qtrue;
  }

  if (remapped)
    G_RemapTeamShaders();
}

/*
 =================
 G_UpdateCvars
 =================
 */
void
G_UpdateCvars(void)
{
  int i;
  cvarTable_t *cv;
  qboolean remapped = qfalse;

  for(i = 0, cv = gameCvarTable;i < gameCvarTableSize;i++, cv++)
  {
    if (cv->vmCvar)
    {
      trap_Cvar_Update(cv->vmCvar);

      if (cv->modificationCount != cv->vmCvar->modificationCount)
      {
        cv->modificationCount = cv->vmCvar->modificationCount;

        if (cv->trackChange)
        {
          trap_SendServerCommand(-1, va(
            "print \"Server: %s changed to %s\n\"", cv->cvarName, cv->vmCvar->string));
          // update serverinfo in case this cvar is passed to clients indirectly
          CalculateRanks();
        }

        if (cv->teamShader)
          remapped = qtrue;
      }
    }
  }

  if (remapped)
    G_RemapTeamShaders();
}

/*
 =================
 G_MapConfigs
 =================
 */
void
G_MapConfigs(const char *mapname)
{

  if (!g_mapConfigs.string[0])
    return;

  if (trap_Cvar_VariableIntegerValue("g_mapConfigsLoaded"))
    return;

  trap_SendConsoleCommand(EXEC_APPEND, va("exec \"%s/default.cfg\"\n", g_mapConfigs.string));

  trap_SendConsoleCommand(EXEC_APPEND, va("exec \"%s/%s.cfg\"\n", g_mapConfigs.string, mapname));

  trap_Cvar_Set("g_mapConfigsLoaded", "1");
}

/*
 ============
 G_InitGame

 ============
 */
void
G_InitGame(int levelTime, int randomSeed, int restart)
{
  int i;
  char data[255];
  //char map[ MAX_STRING_CHARS ];

  srand(randomSeed);

  G_RegisterCvars();

  G_Printf("------- Game Initialization -------\n");
  G_Printf("gamename: %s\n", GAME_VERSION);
  G_Printf("gamedate: %s\n", __DATE__);

  G_ProcessIPBans();

  G_InitMemory();

  // set some level globals
  memset(&level, 0, sizeof(level));
  level.time = levelTime;
  level.startTime = levelTime;
  level.alienStage2Time = level.alienStage3Time = level.humanStage2Time = level.humanStage3Time
      = level.startTime;

  //PathFinding
  level.maxpasos = 100;
  level.selectednode = NULL;

  level.snd_fry = G_SoundIndex("sound/misc/fry.wav"); // FIXME standing in lava / slime

  trap_Cvar_Set("qvm_version", QVM_NAME " " QVM_VERSIONNUM " (" __DATE__ ", " __TIME__ ")");

  if (g_logFile.string[0])
  {
    if (g_logFileSync.integer)
      trap_FS_FOpenFile(g_logFile.string, &level.logFile, FS_APPEND_SYNC);
    else
      trap_FS_FOpenFile(g_logFile.string, &level.logFile, FS_APPEND);

    if (!level.logFile)
      G_Printf("WARNING: Couldn't open logfile: %s\n", g_logFile.string);
    else
    {
      char serverinfo[MAX_INFO_STRING];
      qtime_t qt;
      int t;

      trap_GetServerinfo(serverinfo, sizeof(serverinfo));

      G_LogPrintf("------------------------------------------------------------\n");
      G_LogPrintf("InitGame: %s\n", serverinfo);

      t = trap_RealTime(&qt);
      G_LogPrintf(
        "RealTime: %04i/%02i/%02i %02i:%02i:%02i\n", qt.tm_year + 1900, qt.tm_mon + 1, qt.tm_mday,
        qt.tm_hour, qt.tm_min, qt.tm_sec);

    }
  }
  else
    G_Printf("Not logging to disk\n");

  {
    char map[MAX_CVAR_VALUE_STRING] =
    { "" };

    trap_Cvar_VariableStringBuffer("mapname", map, sizeof(map));
    G_MapConfigs(map);
  }

  // we're done with g_mapConfigs, so reset this for the next map
  trap_Cvar_Set("g_mapConfigsLoaded", "0");

  if (g_admin.string[0])
  {
    G_admin_readconfig(NULL, 0);
  }

  // initialize all entities for this game
  memset(g_entities, 0, MAX_GENTITIES * sizeof(g_entities[0]));
  level.gentities = g_entities;

  // initialize all clients for this game
  level.maxclients = g_maxclients.integer;
  memset(g_clients, 0, MAX_CLIENTS * sizeof(g_clients[0]));
  level.clients = g_clients;

  // set client fields on player ents
  for(i = 0;i < level.maxclients;i++)
    g_entities[i].client = level.clients + i;

  // always leave room for the max number of clients,
  // even if they aren't all used, so numbers inside that
  // range are NEVER anything but clients
  level.num_entities = MAX_CLIENTS;

  // let the server system know where the entites are
  trap_LocateGameData(
    level.gentities, level.num_entities, sizeof(gentity_t), &level.clients[0].ps,
    sizeof(level.clients[0]));

  trap_SetConfigstring(CS_INTERMISSION, "0");

  // update maplog
  G_admin_maplog_update();

  // test to see if a custom buildable layout will be loaded
  G_LayoutSelect();
  
  //Connecte to database here.

  if (Q_stricmp(level.layout, "survival") == 0)
  {
    char map[MAX_CVAR_VALUE_STRING] =
    { "" };
    trap_Cvar_Set("g_survival", "1");
    G_Printf("Survival Mode on.\n");
    
    level.survivalRecords[0] = 30000;
    level.survivalRecords[1] = 60000;
    level.survivalRecords[2] = 90000;

    i = 2;
    trap_Cvar_VariableStringBuffer("mapname", map, sizeof(map));
    //Fetch survival records from server...
    if (trap_mysql_runquery(va(
      "SELECT time FROM games WHERE map = \"%s\" ORDER BY time desc LIMIT 3", map)) == qtrue)
    {
      while(trap_mysql_fetchrow() == qtrue)
      {
        trap_mysql_fetchfieldbyName("time", data, sizeof(data));
        level.survivalRecords[i] = atoi(data);
        i--;
      }
      trap_mysql_finishquery();
    }
  }
  else
  {
    trap_Cvar_Set("g_survival", "0");
  }

  // parse the key/value pairs and spawn gentities
  G_SpawnEntitiesFromString();

  // load up a custom building layout if there is one
  G_LayoutLoad();

  // the map might disable some things
  BG_InitAllowedGameElements();

  // general initialization
  G_FindTeams();

  //TA:
  BG_InitClassOverrides();
  BG_InitBuildableOverrides();
  G_InitDamageLocations();
  G_InitMapRotations();
  G_InitSpawnQueue(&level.alienSpawnQueue);
  G_InitSpawnQueue(&level.humanSpawnQueue);

  if (g_debugMapRotation.integer)
    G_PrintRotations();

  //reset stages
  trap_Cvar_Set("g_alienStage", va("%d", S3));
  trap_Cvar_Set("g_humanStage", va("%d", S3));
  trap_Cvar_Set("g_alienKills", 0);
  trap_Cvar_Set("g_humanKills", 0);
  trap_Cvar_Set("g_suddenDeath", 0);
  level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000;

  //ROTAX
  trap_Cvar_Set("g_ambush", "0");
  trap_Cvar_Set("g_ctn", "0");
  trap_Cvar_Set("g_friendlyFire", "1");
  trap_Cvar_Set("g_friendlyFireAliens", "1");

  ROTACAK_ambush_rebuild_time_temp = 0;
  ROTACAK_ambush_stage = 1;
  ROTACAK_ambush_kills = 0;

  level.bots = 0;
  level.botslots = trap_Cvar_VariableIntegerValue("sv_privateclients");

  G_Printf("-----------------------------------\n");

  G_RemapTeamShaders();

  //TA: so the server counts the spawns without a client attached
  G_CountSpawns();

  G_ResetPTRConnections();

  if (g_lockTeamsAtStart.integer)
  {
    level.alienTeamLocked = qtrue;
    level.humanTeamLocked = qtrue;
    trap_Cvar_Set("g_lockTeamsAtStart", "0");
  }

  ACEND_InitNodes();
  //ACEND_LoadNodes(); Ma function to load from file dont owrkz..
  //ACESP_InitBots(restart); that dont work either.

}

/*
 ==================
 G_ClearVotes

 remove all currently active votes
 ==================
 */
static void
G_ClearVotes(void)
{
  level.voteTime = 0;
  trap_SetConfigstring(CS_VOTE_TIME, "");
  trap_SetConfigstring(CS_VOTE_STRING, "");
  level.teamVoteTime[0] = 0;
  trap_SetConfigstring(CS_TEAMVOTE_TIME, "");
  trap_SetConfigstring(CS_TEAMVOTE_STRING, "");
  level.teamVoteTime[1] = 0;
  trap_SetConfigstring(CS_TEAMVOTE_TIME + 1, "");
  trap_SetConfigstring(CS_TEAMVOTE_STRING + 1, "");
}

/*
 =================
 G_ShutdownGame
 =================
 */
void
G_ShutdownGame(int restart)
{
  // in case of a map_restart
  G_ClearVotes();

  G_Printf("==== ShutdownGame ====\n");

  if (level.logFile)
  {
    G_LogPrintf("ShutdownGame:\n");
    G_LogPrintf("------------------------------------------------------------\n");
    trap_FS_FCloseFile(level.logFile);
  }

  // write all the client session data so we can get it back
  G_WriteSessionData();

  G_admin_cleanup();
  G_admin_namelog_cleanup();

  level.restarted = qfalse;
  level.surrenderTeam = PTE_NONE;
  trap_SetConfigstring(CS_WINNER, "");
}

//===================================================================

void QDECL
Com_Error(int level, const char *error, ...)
{
  va_list argptr;
  char text[1024];

  va_start(argptr, error);
  vsprintf(text, error, argptr);
  va_end(argptr);

  G_Error("%s", text);
}

void QDECL
Com_Printf(const char *msg, ...)
{
  va_list argptr;
  char text[1024];

  va_start(argptr, msg);
  vsprintf(text, msg, argptr);
  va_end(argptr);

  G_Printf("%s", text);
}

/*
 ========================================================================

 PLAYER COUNTING / SCORE SORTING

 ========================================================================
 */

/*
 =============
 SortRanks

 =============
 */
int QDECL
SortRanks(const void *a, const void *b)
{
  gclient_t *ca, *cb;

  ca = &level.clients[*(int *) a];
  cb = &level.clients[*(int *) b];

  // then sort by score
  if (ca->pers.score > cb->pers.score)
    return -1;
  else if (ca->pers.score < cb->pers.score)
    return 1;
  else
    return 0;
}

/*
 ============
 G_InitSpawnQueue

 Initialise a spawn queue
 ============
 */
void
G_InitSpawnQueue(spawnQueue_t *sq)
{
  int i;

  sq->back = sq->front = 0;
  sq->back = QUEUE_MINUS1(sq->back);

  //0 is a valid clientNum, so use something else
  for(i = 0;i < MAX_CLIENTS;i++)
    sq->clients[i] = -1;
}

/*
 ============
 G_GetSpawnQueueLength

 Return tha length of a spawn queue
 ============
 */
int
G_GetSpawnQueueLength(spawnQueue_t *sq)
{
  int length = sq->back - sq->front + 1;

  while(length < 0)
    length += MAX_CLIENTS;

  while(length >= MAX_CLIENTS)
    length -= MAX_CLIENTS;

  return length;
}

/*
 ============
 G_PopSpawnQueue

 Remove from front element from a spawn queue
 ============
 */
int
G_PopSpawnQueue(spawnQueue_t *sq)
{
  int clientNum = sq->clients[sq->front];

  if (G_GetSpawnQueueLength(sq) > 0)
  {
    sq->clients[sq->front] = -1;
    sq->front = QUEUE_PLUS1(sq->front);
    g_entities[clientNum].client->ps.pm_flags &= ~PMF_QUEUED;

    return clientNum;
  }
  else
    return -1;
}

/*
 ============
 G_PeekSpawnQueue

 Look at front element from a spawn queue
 ============
 */
int
G_PeekSpawnQueue(spawnQueue_t *sq)
{
  return sq->clients[sq->front];
}

/*
 ============
 G_SearchSpawnQueue

 Look to see if clientNum is already in the spawnQueue
 ============
 */
qboolean
G_SearchSpawnQueue(spawnQueue_t *sq, int clientNum)
{
  int i;

  for(i = 0;i < MAX_CLIENTS;i++)
    if (sq->clients[i] == clientNum)
      return qtrue;
  return qfalse;
}

/*
 ============
 G_PushSpawnQueue

 Add an element to the back of the spawn queue
 ============
 */
qboolean
G_PushSpawnQueue(spawnQueue_t *sq, int clientNum)
{
  // don't add the same client more than once
  if (G_SearchSpawnQueue(sq, clientNum))
    return qfalse;

  sq->back = QUEUE_PLUS1(sq->back);
  sq->clients[sq->back] = clientNum;

  g_entities[clientNum].client->ps.pm_flags |= PMF_QUEUED;
  return qtrue;
}

/*
 ============
 G_RemoveFromSpawnQueue

 remove a specific client from a spawn queue
 ============
 */
qboolean
G_RemoveFromSpawnQueue(spawnQueue_t *sq, int clientNum)
{
  int i = sq->front;

  if (G_GetSpawnQueueLength(sq))
  {
    do
    {
      if (sq->clients[i] == clientNum)
      {
        //and this kids is why it would have
        //been better to use an LL for internal
        //representation
        do
        {
          sq->clients[i] = sq->clients[QUEUE_PLUS1(i)];

          i = QUEUE_PLUS1(i);
        }
        while(i != QUEUE_PLUS1(sq->back));

        sq->back = QUEUE_MINUS1(sq->back);
        g_entities[clientNum].client->ps.pm_flags &= ~PMF_QUEUED;

        return qtrue;
      }

      i = QUEUE_PLUS1(i);
    }
    while(i != QUEUE_PLUS1(sq->back));
  }

  return qfalse;
}

/*
 ============
 G_GetPosInSpawnQueue

 Get the position of a client in a spawn queue
 ============
 */
int
G_GetPosInSpawnQueue(spawnQueue_t *sq, int clientNum)
{
  int i = sq->front;

  if (G_GetSpawnQueueLength(sq))
  {
    do
    {
      if (sq->clients[i] == clientNum)
      {
        if (i < sq->front)
          return i + MAX_CLIENTS - sq->front;
        else
          return i - sq->front;
      }

      i = QUEUE_PLUS1(i);
    }
    while(i != QUEUE_PLUS1(sq->back));
  }

  return -1;
}

/*
 ============
 G_PrintSpawnQueue

 Print the contents of a spawn queue
 ============
 */
void
G_PrintSpawnQueue(spawnQueue_t *sq)
{
  int i = sq->front;
  int length = G_GetSpawnQueueLength(sq);

  G_Printf("l:%d f:%d b:%d    :", length, sq->front, sq->back);

  if (length > 0)
  {
    do
    {
      if (sq->clients[i] == -1)
        G_Printf("*:");
      else
        G_Printf("%d:", sq->clients[i]);

      i = QUEUE_PLUS1(i);
    }
    while(i != QUEUE_PLUS1(sq->back));
  }

  G_Printf("\n");
}

/*
 ============
 G_SpawnClients

 Spawn queued clients
 ============
 */
void
G_SpawnClients(pTeam_t team)
{
  int clientNum;
  gentity_t *ent, *spawn;
  vec3_t spawn_origin, spawn_angles;
  spawnQueue_t *sq = NULL;
  int numSpawns = 0;

  if (g_doWarmup.integer && (g_warmupMode.integer == 1 || g_warmupMode.integer == 2) && level.time
      - level.startTime < g_warmup.integer * 1000)
  {
    return;
  }
  if (team == PTE_ALIENS)
  {
    sq = &level.alienSpawnQueue;
    numSpawns = level.numAlienSpawns;
  }
  else if (team == PTE_HUMANS)
  {
    sq = &level.humanSpawnQueue;
    numSpawns = level.numHumanSpawns;
  }

  if (G_GetSpawnQueueLength(sq) > 0 && numSpawns > 0)
  {
    clientNum = G_PeekSpawnQueue(sq);
    ent = &g_entities[clientNum];

    if ((spawn = G_SelectTremulousSpawnPoint(
      team, ent->client->pers.lastDeathLocation, spawn_origin, spawn_angles, ent)))
    {
      clientNum = G_PopSpawnQueue(sq);

      if (clientNum < 0)
        return;

      ent = &g_entities[clientNum];

      ent->client->sess.sessionTeam = TEAM_FREE;
      if (!(ent->r.svFlags & SVF_BOT))
      {
        ClientUserinfoChanged(clientNum);
      }
      ClientSpawn(ent, spawn, spawn_origin, spawn_angles);
    }
  }
}

void
botAttack(gentity_t *enemy, int metaMode)
{
  gentity_t *ent;
  int i;
  int maxbots = 0;
  int controledbots = 0;
  if (!enemy)
    return;
  if (!enemy->client)
    return;
  if (enemy->client->ps.stats[STAT_HEALTH] <= 0 || enemy->health <= 0)
    return;

  switch(metaMode)
  {
    case ATTACK_RAMBO:
      maxbots = 20;
      break;
    case ATTACK_CAMPER:
      maxbots = 20;
      break;
    case ATTACK_ALL:
      maxbots = level.botslots;
      break;
  }
  for(i = 0;i < level.botslots;i++)
  {
    ent = &g_entities[i];

    if (!ent)
      continue;
    if (!ent->client)
      continue;
    if (ent->client->sess.sessionTeam == TEAM_SPECTATOR)
      continue;
    if (ent->client->ps.stats[STAT_PTEAM] != PTE_ALIENS)
      continue;
    if (ent->client->ps.stats[STAT_HEALTH] <= 0 || ent->health <= 0)
      continue;
    if (ent->botEnemy)
      continue;

    ACESP_SetupBotState(ent);
    ent->botCommand = BOT_FOLLOW_PATH;
    ent->botEnemy = enemy;
    ent->bs.goalEntity = enemy;
    ent->bs.moveTarget = enemy;
    ent->botMetaMode = metaMode;
    controledbots++;
    if (controledbots >= (maxbots))
      break;
  }
}

/*
 ===========
 ClientSpawn

 Spawn Bots closer to the enemy.
 ============
 */
void
botSpawn(gentity_t *ent)
{
  int index;
  vec3_t spawn_origin, spawn_angles;
  gclient_t *client;
  int i;
  clientPersistant_t saved;
  clientSession_t savedSess;
  int persistant[MAX_PERSISTANT];
  gentity_t *spawnPoint = NULL;
  int flags;
  int savedPing;
  int teamLocal;
  int eventSequence;
  char userinfo[MAX_INFO_STRING];
  int maxAmmo, maxClips;
  weapon_t weapon;

  index = ent - g_entities;
  client = ent->client;

  teamLocal = client->pers.teamSelection;

  if (ent->botEnemy)
  {
    if (ent->botEnemy->client)
    {
    }
  }

  //Reset Everything For path Finding
  ent->bs.currentNode = -1;
  ent->bs.goalNode = -1;
  ent->bs.lastNode = -1;
  ent->bs.nextNode = -1;
  ent->bs.isCrouchJumping = qfalse;
  ent->bs.isJumping = qfalse;
  ent->bs.isLongJumping = qfalse;
  ent->bs.isUsingLadder = qfalse;

  if (g_survival.integer)
  {
    ent->bs.state = STATE_WANDER;
    ent->botCommand = BOT_FOLLOW_PATH;
  }

  ent->lastTimeSawEnemy = level.time;

  //if client is dead and following teammate, stop following before spawning
  if (ent->client->sess.spectatorClient != -1)
  {
    ent->client->sess.spectatorClient = -1;
    ent->client->sess.spectatorState = SPECTATOR_FREE;
  }

  if (ent->nextSpawnLocation != NULL)
    VectorCopy(ent->nextSpawnLocation, spawn_origin);

  spawn_angles[ROLL] = 0;

  client->pers.teamState.state = TEAM_ACTIVE;

  // toggle the teleport bit so the client knows to not lerp
  flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED);
  flags ^= EF_TELEPORT_BIT;
  G_UnlaggedClear(ent);

  // clear everything but the persistant data

  saved = client->pers;
  savedSess = client->sess;
  savedPing = client->ps.ping;

  for(i = 0;i < MAX_PERSISTANT;i++)
    persistant[i] = client->ps.persistant[i];

  eventSequence = client->ps.eventSequence;
  memset(client, 0, sizeof(*client));

  if (ent->r.svFlags & SVF_BOT)
  {
    botWalk(ent, 0);
    botJump(ent, 0);
  }

  client->pers = saved;
  client->sess = savedSess;
  client->ps.ping = savedPing;
  client->lastkilled_client = -1;

  for(i = 0;i < MAX_PERSISTANT;i++)
    client->ps.persistant[i] = persistant[i];

  client->ps.eventSequence = eventSequence;

  // increment the spawncount so the client will detect the respawn
  client->ps.persistant[PERS_SPAWN_COUNT]++;
  client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam;

  // restore really persistant things
  client->ps.persistant[PERS_SCORE] = client->pers.score;
  client->ps.persistant[PERS_CREDIT] = client->pers.credit;

  client->airOutTime = level.time + 12000;

  trap_GetUserinfo(index, userinfo, sizeof(userinfo));
  client->ps.eFlags = flags;

  //Com_Printf( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection );

  ent->s.groundEntityNum = ENTITYNUM_NONE;
  ent->client = &level.clients[index];
  ent->takedamage = qtrue;
  ent->inuse = qtrue;
  ent->classname = "player";
  ent->r.contents = CONTENTS_BODY;
  ent->clipmask = MASK_PLAYERSOLID;
  ent->die = player_die;
  ent->waterlevel = 0;
  ent->watertype = 0;
  ent->flags = 0;

  //TA: calculate each client's acceleration
  ent->evaluateAcceleration = qtrue;

  client->ps.stats[STAT_WEAPONS] = 0;
  client->ps.stats[STAT_WEAPONS2] = 0;
  client->ps.stats[STAT_SLOTS] = 0;

  client->ps.eFlags = flags;
  client->ps.clientNum = index;

  BG_FindBBoxForClass(ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL);

  if(g_survival.integer)
  {
    client->pers.maxHealth = client->ps.stats[STAT_MAX_HEALTH] = 300;
  }
  else
  {
    client->pers.maxHealth = client->ps.stats[STAT_MAX_HEALTH] = 100;
  }

  //client->pers.maxHealth += (20 * (level.numConnectedClients - level.bots));

  ent->client->ps.stats[STAT_PCLASS] = ent->client->pers.classSelection;
  ent->client->ps.stats[STAT_PTEAM] = ent->client->pers.teamSelection;

  ent->client->ps.stats[STAT_BUILDABLE] = BA_NONE;
  ent->client->ps.stats[STAT_STATE] = 0;
  VectorSet(ent->client->ps.grapplePoint, 0.0f, 0.0f, 1.0f);

  // health will count down towards max_health
  ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH]; //* 1.25;


  //clear the credits array
  for(i = 0;i < MAX_CLIENTS;i++)
    ent->credits[i] = 0;

  client->ps.stats[STAT_STAMINA] = MAX_STAMINA;

  G_SetOrigin(ent, spawn_origin);
  VectorCopy(spawn_origin, client->ps.origin);

#define UP_VEL  150.0f
#define F_VEL   50.0f

  //give aliens some spawn velocity
  spawn_angles[YAW] += 180.0f;
  AngleNormalize360(spawn_angles[YAW]);

  // the respawned flag will be cleared after the attack and jump keys come up
  client->ps.pm_flags |= PMF_RESPAWNED;

  trap_GetUsercmd(client - level.clients, &ent->client->pers.cmd);
  G_SetClientViewAngle(ent, spawn_angles);

  if (!(client->sess.sessionTeam == TEAM_SPECTATOR))
  {
    /*G_KillBox( ent );*///blame this if a newly spawned client gets stuck in another
    trap_LinkEntity(ent);

    // force the base weapon up
    client->ps.weapon = WP_NONE;
    client->ps.weaponstate = WEAPON_READY;
  }

  BG_AddWeaponToInventory(WP_ZOMBIE_BITE, client->ps.stats);

  // don't allow full run speed for a bit
  client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
  client->ps.pm_time = 100;

  client->respawnTime = level.time;
  client->lastKillTime = level.time;

  client->inactivityTime = level.time + g_inactivity.integer * 1000;
  client->latched_buttons = 0;

  // set default animations
  client->ps.torsoAnim = TORSO_STAND;
  client->ps.legsAnim = LEGS_IDLE;

  if (level.intermissiontime)
    MoveClientToIntermission(ent);
  else
  {
    client->ps.weapon = 1;
    for(i = WP_NUM_WEAPONS - 1;i > 0;i--)
    {
      if (BG_InventoryContainsWeapon(i, client->ps.stats))
      {
        client->ps.weapon = i;
        break;
      }
    }
  }

  // run a client frame to drop exactly to the floor,
  // initialize animations and other things
  client->ps.commandTime = level.time - 100;
  ent->client->pers.cmd.serverTime = level.time;
  ClientThink(ent - g_entities);

  // positively link the client, even if the command times are weird
  if (client->sess.sessionTeam != TEAM_SPECTATOR)
  {
    BG_PlayerStateToEntityState(&client->ps, &ent->s, qtrue);
    VectorCopy(ent->client->ps.origin, ent->r.currentOrigin);
    trap_LinkEntity(ent);
  }

  //TA: must do this here so the number of active clients is calculated
  CalculateRanks();

  // run the presend to set anything else
  ClientEndFrame(ent);

  ent->client->pers.classSelection = PCL_HUMAN;
  ent->client->ps.stats[STAT_PCLASS] = PCL_HUMAN;
  ent->client->pers.humanItemSelection = WP_ZOMBIE_BITE;

  // clear entity state values
  BG_PlayerStateToEntityState(&client->ps, &ent->s, qtrue);
}

void
G_SpawnBots(void)
{
  int clientNum;
  gentity_t *ent;
  vec3_t spawn_origin, spawn_angles;
  spawnQueue_t *sq = NULL;
  vec3_t cmins, cmaxs;

  sq = &level.alienSpawnQueue;

  if (G_GetSpawnQueueLength(sq) > 0)
  {
    clientNum = G_PeekSpawnQueue(sq);
    ent = &g_entities[clientNum];

    //Our enemy is valid?
    if (!ent->botEnemy || ent->botEnemy->health <= 0 || !ent->botEnemy->client
        || ent->botEnemy->client->pers.connected == CON_DISCONNECTED)
    {
      //Find a new enemy.
      botSelectEnemy(ent);
    }

    if (ent->botEnemy && ent->botEnemy->health > 0 && ent->botEnemy->client
        && ent->botEnemy->client->pers.connected != CON_DISCONNECTED
        && ACEND_FindClosestSpawnNodeToEnemy(ent))
    {
      clientNum = G_PopSpawnQueue(sq);

      if (clientNum < 0)
        return;

      ent = &g_entities[clientNum];
      ent->client->sess.sessionTeam = TEAM_FREE;

      botSpawn(ent);
    }
    else
    {
      //Failed to get a good node haaa...
      ent->botEnemy = NULL;
      G_SpawnClients(PTE_ALIENS);
    }
  }
}
//The IA master mind
void
G_Director()
{
  //In order the worse first
  gentity_t *ent;
  gentity_t *ent2;
  gentity_t *randomPlayer = NULL;
  gentity_t *rambo; //Like to leave the team alone
  gentity_t *camper; //Like to hides and dont fight
  int i, j;

  if (!g_survival.integer)
    return;

  G_SpawnBots();

  if ((level.time-level.startTime) >= 600000  && (level.time-level.startTime) < 601000)
  {
    //trap_SendServerCommand(-1, "print \"^3Alien Team Unlocked.\n\"");
    level.alienTeamLocked = qfalse;
    //Loop trought all players and putteam the ones dead on the alien team.
    for(i = level.botslots;i < level.botslots + level.numConnectedClients;i++)
    {
      ent = &g_entities[i];

      if (!ent)
        continue;
      if (!ent->client)
        continue;
      if (ent->client->ps.stats[STAT_HEALTH] > 0 || ent->health > 0)
        continue;
      if (ent->client->pers.connected != CON_CONNECTED)
        continue;

      trap_SendServerCommand(-1, "print \"^3Join ^1Zombies /team aliens \n\"");
      G_ChangeTeam(ent, PTE_ALIENS);
    }
  }

  if (level.time % 1000 % 15 != 0) // every 5 seconds lolz.
  {
    return;
  }

  for(i = 0;i < level.bots;i++)
  {
    ent = &g_entities[i];

    if (!ent)
      continue;
    if (!(ent->r.svFlags & SVF_BOT))
      continue;
    if (!ent->client)
      continue;
    if (ent->client->sess.sessionTeam == TEAM_SPECTATOR)
      continue;
    if (ent->client->ps.stats[STAT_PTEAM] != PTE_ALIENS)
      continue;
    if (ent->client->ps.stats[STAT_HEALTH] <= 0 || ent->health <= 0)
      continue;
    if ((ent->lastTimeSawEnemy + 23000) > level.time)
      continue;

    G_Damage(ent, NULL, NULL, NULL, NULL, 500, 0, MOD_UNKNOWN);
  }

  ent = ent2 = randomPlayer = rambo = camper = NULL;

  //To detect the rambo, what is a rambo
  //a rambo is the player go and run away from
  //it mates, so to detect him we just need to check around
  //if there are not teammaters around we have a rambo, and it must die.
  for(i = level.botslots;i < level.botslots + level.numConnectedClients;i++)
  {
    ent = &g_entities[i];
    if (!ent)
      continue;
    if (!ent->client)
      continue;
    if (ent->client->sess.sessionTeam == TEAM_SPECTATOR)
      continue;
    if (ent->client->ps.stats[STAT_PTEAM] != PTE_HUMANS)
      continue;
    if (ent->client->ps.stats[STAT_HEALTH] <= 0 || ent->health <= 0)
      continue;

    rambo = ent;

    for(j = level.botslots;j < level.botslots + level.numConnectedClients;j++)
    {
      ent2 = &g_entities[j];
      if (!ent2)
        continue;
      if (!ent2->client)
        continue;
      if (i == j)
        continue;
      if (ent2->client->sess.sessionTeam == TEAM_SPECTATOR)
        continue;
      if (ent2->client->ps.stats[STAT_PTEAM] != PTE_HUMANS)
        continue;
      if (ent2->client->ps.stats[STAT_HEALTH] <= 0 || ent2->health <= 0)
        continue;

      //G_Printf("Checking visibility from %s to %s\n", ent->client->pers.netname, ent2->client->pers.netname);
      if (G_Visible(ent, ent2))
      {
        rambo = NULL;
        break;
      }
    }
    if (rambo)
      break;
  }
  if (rambo)
  {
    botAttack(rambo, ATTACK_RAMBO);
  }
  for(i = level.botslots;i < level.botslots + level.numConnectedClients;i++)
  {
    ent = &g_entities[i];
    if (!ent)
      continue;
    if (!ent->client)
      continue;
    if (ent->client->sess.sessionTeam == TEAM_SPECTATOR)
      continue;
    if (ent->client->ps.stats[STAT_PTEAM] != PTE_HUMANS)
      continue;
    if (ent->client->ps.stats[STAT_HEALTH] <= 0 || ent->health <= 0)
      continue;

    if (ent->lastTimeSeen == 0)
    {
      ent->lastTimeSeen = level.time + 20000; //Give the player some time
      continue;
    }

    if ((ent->lastTimeSeen + 15000) < level.time) //Time to camp.

    {
      ent->lastTimeSeen = level.time;
      camper = ent;
      break;
    }
  }
  if (camper)
  {
    botAttack(camper, ATTACK_CAMPER);
  }
  //Every Minut Send Some bots to a random player.
  if (level.time % 1000 % 120 == 0)
  {
    for(i = level.botslots;i < level.botslots + level.numConnectedClients;i++)
    {
      ent = &g_entities[i];

      if (!ent)
        continue;
      if (!ent->client)
        continue;
      if (ent->client->sess.sessionTeam == TEAM_SPECTATOR)
        continue;
      if (ent->client->ps.stats[STAT_PTEAM] != PTE_HUMANS)
        continue;
      if (ent->client->ps.stats[STAT_HEALTH] <= 0 || ent->health <= 0)
        continue;

      randomPlayer = ent;
      break;
    }
    if (!randomPlayer)
      return;
    botAttack(randomPlayer, ATTACK_ALL);
  }
}

void
G_addBot()
{
  if (level.numAlienSpawns < 1)
    return;

  if ((level.time - level.startTime) < 94000 && (level.time - level.startTime) > 90000)
  {
    if (g_survival.integer)
    {
      G_KillStructuresSurvival();
      trap_SendServerCommand(-1, "cp \"^2Spawns Down!\"");
    }
  }
  if ((level.time - level.startTime) < 10000 && (level.time - level.startTime) > 9000)
  {
    if (g_survival.integer)
    {
      trap_SendServerCommand(-1, "cp \"^1Survival Mode!\"");
    }
    else
    {
      trap_SendServerCommand(-1, "cp \"^2Clean the Map mode!\"");
    }
  }

  if ((level.time - level.startTime) < 6000 && (level.time - level.startTime) > 5000)
  {
    trap_SendServerCommand(-1, "cp \"^7Add ^7{^1Z^7} name if you like this server\"");
  }

  if (level.lastBotTime > level.time)
  {
    return;
  }
  //if (!g_survival.integer) return;
  if (g_survival.integer)
  {
    level.lastBotTime = level.time + 3000;
    if ((level.time - level.startTime) / 1000 / 60 > 4) //4 Minutes

    {
      level.lastBotTime = level.time + 2000;
    }
    if ((level.time - level.startTime) / 1000 / 60 > 6) //6 Minutes

    {
      level.lastBotTime = level.time + 1000;
    }
  }
  else
  {
    level.lastBotTime = level.time + 1000;
  }
  level.botsLevel++;
  if (level.bots < 25)
  {
    G_BotAdd(va("^1Zombie%d", level.bots), PTE_ALIENS, level.botsLevel, NULL);
  }
}
void
G_CalculateSurvivalRecords(void)
{
  int i;
  if (g_survival.integer)
  {
    for(i = 0;i < 3;i++)
    {
      if (level.time - level.startTime > level.survivalRecords[i] && level.survivalRecordsBroke[i]
          < 1)
      {
        level.survivalRecordsBroke[i] = 1;
        if (i == 0)
        {
          trap_SendServerCommand(-1, "cp \"^7New Bronze Record!\n\"");
          //  trap_SendServerCommand(-1, va("print \"^7New Bronze Record %d!\n\"", level.survivalRecords[i]));
        }
        if (i == 1)
        {
          trap_SendServerCommand(-1, "cp \"^7New Silver Record!\n\"");
          //  trap_SendServerCommand(-1, "print \"^7New Silver Record!\n\"");
        }
        if (i == 2)
        {
          trap_SendServerCommand(-1, "cp \"^7New Gold Record!\n\"");
          //  trap_SendServerCommand(-1, "print \"^7New Gold Record!\n\"");
        }
      }
    }
  }
}

void
G_UpdateCamper(void)
{
}

/*
 ============
 G_CountSpawns

 Counts the number of spawns for each team
 ============
 */
void
G_CountSpawns(void)
{
  int i;
  gentity_t *ent;

  level.numAlienSpawns = 0;
  level.numHumanSpawns = 0;

  if (g_layoutmaking.integer == 1)
  {
    level.numAlienSpawns = 1;
    level.numHumanSpawns = 1;
  }

  for(i = 1, ent = g_entities + i;i < level.num_entities;i++, ent++)
  {
    if (!ent->inuse)
      continue;

    if ((ent->s.modelindex == BA_H_SPAWN && ent->health > 0 && ent->biteam == BIT_ALIENS)
        || ((ent->s.modelindex == BA_A_SPAWN) && ent->health > 0))
    {
      level.numAlienSpawns++;
    }
    if (ent->s.modelindex == BA_H_SPAWN && ent->health > 0 && ent->biteam == BIT_HUMANS)
    {
      /*if(level.time < 10000)
       {
       trap_SendServerCommand( -1, "print \"Human Spawn found!\n\"");
       }*/
      level.numHumanSpawns++;
    }
  }

  //let the client know how many spawns there are
  trap_SetConfigstring(CS_SPAWNS, va("%d %d", level.numAlienSpawns, level.numHumanSpawns));
}

/*
 ============
 G_TimeTilSuddenDeath
 ============
 */
int
G_TimeTilSuddenDeath(void)
{
  if ((!g_suddenDeathTime.integer && level.suddenDeathBeginTime == 0) || level.suddenDeathBeginTime
      < 0)
    return 999999999; // Always some time away

  return ((level.suddenDeathBeginTime) - (level.time - level.startTime));
}

#define PLAYER_COUNT_MOD 5.0f

/*
 ============
 G_CalculateBuildPoints

 Recalculate the quantity of building points available to the teams
 ============
 */
void
G_CalculateBuildPoints(void)
{
  int i;
  buildable_t buildable;
  gentity_t *ent;
  int localHTP = g_humanBuildPoints.integer, localATP = g_alienBuildPoints.integer;

  // g_suddenDeath sets what state we want it to be.
  // level.suddenDeath says whether we've calculated BPs at the 'start' of SD or not

  // reset if SD was on, but now it's off
  if (!g_suddenDeath.integer && level.suddenDeath)
  {
    level.suddenDeath = qfalse;
    level.suddenDeathWarning = 0;
    level.suddenDeathBeginTime = -1;
    if ((level.time - level.startTime) < (g_suddenDeathTime.integer * 60000))
      level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000;
    else
      level.suddenDeathBeginTime = -1;
  }

  if (!level.suddenDeath)
  {
    if (g_suddenDeath.integer || G_TimeTilSuddenDeath() <= 0) //Conditions to enter SD

    {
      //begin sudden death
      if (level.suddenDeathWarning < TW_PASSED)
      {
        trap_SendServerCommand(-1, "cp \"Sudden Death!\"");
        G_LogPrintf("Beginning Sudden Death (Mode %d)\n", g_suddenDeathMode.integer);
        localHTP = 0;
        localATP = 0;

        if (g_suddenDeathMode.integer == SDMODE_SELECTIVE)
        {
          for(i = 1, ent = g_entities + i;i < level.num_entities;i++, ent++)
          {
            if (ent->s.eType != ET_BUILDABLE)
              continue;

            if (BG_FindReplaceableTestForBuildable(ent->s.modelindex))
            {
              int t = ent->biteam; //BG_FindTeamForBuildable( ent->s.modelindex );

              if (t == BIT_HUMANS)
                localHTP += BG_FindBuildPointsForBuildable(ent->s.modelindex);
              else if (t == BIT_ALIENS)
                localATP += BG_FindBuildPointsForBuildable(ent->s.modelindex);
            }
          }
        }
        level.suddenDeathHBuildPoints = localHTP;
        level.suddenDeathABuildPoints = localATP;
        level.suddenDeathBeginTime = level.time;
        level.suddenDeath = qtrue;
        trap_Cvar_Set("g_suddenDeath", "1");

        level.suddenDeathWarning = TW_PASSED;
      }
    }
    else
    {
      //warn about sudden death
      if ((G_TimeTilSuddenDeath() <= 60000) && (level.suddenDeathWarning < TW_IMMINENT))
      {
        trap_SendServerCommand(-1, va(
          "cp \"Sudden Death in %d seconds!\"", (int) (G_TimeTilSuddenDeath() / 1000)));
        level.suddenDeathWarning = TW_IMMINENT;
      }
    }
  }

  //set BP at each cycle
  if (g_suddenDeath.integer)
  {
    localHTP = level.suddenDeathHBuildPoints;
    localATP = level.suddenDeathABuildPoints;
  }
  else
  {
    localHTP = g_humanBuildPoints.integer;
    localATP = g_alienBuildPoints.integer;
  }

  level.humanBuildPoints = level.humanBuildPointsPowered = localHTP;
  level.alienBuildPoints = localATP;

  level.reactorPresent = qfalse;
  level.overmindPresent = qfalse;

  for(i = 1, ent = g_entities + i;i < level.num_entities;i++, ent++)
  {
    if (!ent->inuse)
      continue;

    if (ent->s.eType != ET_BUILDABLE)
      continue;

    /*if( g_survival.integer && ent->spawned && ent->biteam == BIT_HUMANS && !ent->powered && ent->health > 0 && ent->s.modelindex != BA_H_REACTOR)
     {
     G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
     }*/

    buildable = ent->s.modelindex;

    if (buildable != BA_NONE)
    {
      if (buildable == BA_H_REACTOR && ent->spawned && ent->health > 0)
        level.reactorPresent = qtrue;

      if (buildable == BA_A_OVERMIND && ent->spawned && ent->health > 0)
        level.overmindPresent = qtrue;

      if (!g_suddenDeath.integer || BG_FindReplaceableTestForBuildable(buildable))
      {
        if (ent->biteam == BIT_HUMANS)
        {
          level.humanBuildPoints -= BG_FindBuildPointsForBuildable(buildable);
          if (ent->powered)
            level.humanBuildPointsPowered -= BG_FindBuildPointsForBuildable(buildable);
        }
        else
        {
          level.alienBuildPoints -= BG_FindBuildPointsForBuildable(buildable);
        }
      }
    }
  }

  if (level.humanBuildPoints < 0)
  {
    localHTP -= level.humanBuildPoints;
    level.humanBuildPointsPowered -= level.humanBuildPoints;
    level.humanBuildPoints = 0;
  }

  if (level.alienBuildPoints < 0)
  {
    localATP -= level.alienBuildPoints;
    level.alienBuildPoints = 0;
  }

  trap_SetConfigstring(CS_BUILDPOINTS, va(
    "%d %d %d %d %d", level.alienBuildPoints, localATP, level.humanBuildPoints, localHTP,
    level.humanBuildPointsPowered));

  //may as well pump the stages here too

  //  {
  //    float alienPlayerCountMod = level.averageNumAlienClients / PLAYER_COUNT_MOD;
  //    float humanPlayerCountMod = level.averageNumHumanClients / PLAYER_COUNT_MOD;
  //    int alienNextStageThreshold, humanNextStageThreshold;
  //
  //    if (alienPlayerCountMod < 0.1f)
  //      alienPlayerCountMod = 0.1f;
  //
  //    if (humanPlayerCountMod < 0.1f)
  //      humanPlayerCountMod = 0.1f;
  //
  //    if (g_alienStage.integer == S1 && g_alienMaxStage.integer > S1)
  //      alienNextStageThreshold = (int) (ceil((float) g_alienStage2Threshold.integer * alienPlayerCountMod));
  //    else if (g_alienStage.integer == S2 && g_alienMaxStage.integer > S2)
  //      alienNextStageThreshold = (int) (ceil((float) g_alienStage3Threshold.integer * alienPlayerCountMod));
  //    else
  //      alienNextStageThreshold = -1;
  //
  //    if (g_humanStage.integer == S1 && g_humanMaxStage.integer > S1)
  //      humanNextStageThreshold = (int) (ceil((float) g_humanStage2Threshold.integer * humanPlayerCountMod));
  //    else if (g_humanStage.integer == S2 && g_humanMaxStage.integer > S2)
  //      humanNextStageThreshold = (int) (ceil((float) g_humanStage3Threshold.integer * humanPlayerCountMod));
  //    else
  //      humanNextStageThreshold = -1;
  //
  //    trap_SetConfigstring(CS_STAGES, va("%d %d %d %d %d %d", g_alienStage.integer, g_humanStage.integer, g_alienKills.integer, g_humanKills.integer,
  //        alienNextStageThreshold, humanNextStageThreshold));
  //  }
}

/*
 ============
 G_CalculateStages
 ============
 */
void
G_CalculateStages(void)
{
  //  trap_Cvar_Set("g_alienStage", va("%d", S3));
  //  trap_Cvar_Set("g_humanStage", va("%d", S3));
  //  float alienPlayerCountMod = level.averageNumAlienClients / PLAYER_COUNT_MOD;
  //  float humanPlayerCountMod = level.averageNumHumanClients / PLAYER_COUNT_MOD;
  //  static int lastAlienStageModCount = 1;
  //  static int lastHumanStageModCount = 1;
  //
  //  if (alienPlayerCountMod < 0.1f)
  //    alienPlayerCountMod = 0.1f;
  //
  //  if (humanPlayerCountMod < 0.1f)
  //    humanPlayerCountMod = 0.1f;
  //
  //  if (g_alienKills.integer >= (int) (ceil((float) g_alienStage2Threshold.integer * alienPlayerCountMod)) && g_alienStage.integer == S1
  //      && g_alienMaxStage.integer > S1)
  //  {
  //    trap_Cvar_Set("g_alienStage", va("%d", S2));
  //    level.alienStage2Time = level.time;
  //    lastAlienStageModCount = g_alienStage.modificationCount;
  //    G_LogPrintf("Stage: A 2: Aliens reached Stage 2\n");
  //
  //    g_humanKills.integer = (int) (ceil((float) g_humanStage2Threshold.integer * humanPlayerCountMod)) + 1;
  //  }
  //
  //  if (g_alienKills.integer >= (int) (ceil((float) g_alienStage3Threshold.integer * alienPlayerCountMod)) && g_alienStage.integer == S2
  //      && g_alienMaxStage.integer > S2)
  //  {
  //    trap_Cvar_Set("g_alienStage", va("%d", S3));
  //    level.alienStage3Time = level.time;
  //    lastAlienStageModCount = g_alienStage.modificationCount;
  //    G_LogPrintf("Stage: A 3: Aliens reached Stage 3\n");
  //
  //    g_humanKills.integer = (int) (ceil((float) g_humanStage3Threshold.integer * humanPlayerCountMod)) + 1;
  //  }
  //
  //  if (g_humanKills.integer >= (int) (ceil((float) g_humanStage2Threshold.integer * humanPlayerCountMod)) && g_humanStage.integer == S1
  //      && g_humanMaxStage.integer > S1)
  //  {
  //    trap_Cvar_Set("g_humanStage", va("%d", S2));
  //    level.humanStage2Time = level.time;
  //    lastHumanStageModCount = g_humanStage.modificationCount;
  //    G_LogPrintf("Stage: H 2: Humans reached Stage 2\n");
  //
  //    g_alienKills.integer = (int) (ceil((float) g_alienStage2Threshold.integer * alienPlayerCountMod)) + 1;
  //  }
  //
  //  if (g_humanKills.integer >= (int) (ceil((float) g_humanStage3Threshold.integer * humanPlayerCountMod)) && g_humanStage.integer == S2
  //      && g_humanMaxStage.integer > S2)
  //  {
  //    trap_Cvar_Set("g_humanStage", va("%d", S3));
  //    level.humanStage3Time = level.time;
  //    G_LogPrintf("Stage: H 3: Humans reached Stage 3\n");
  //    lastHumanStageModCount = g_humanStage.modificationCount;
  //
  //    g_alienKills.integer = (int) (ceil((float) g_alienStage3Threshold.integer * alienPlayerCountMod)) + 1;
  //  }
  //
  //  if (g_alienStage.modificationCount > lastAlienStageModCount)
  //  {
  //    G_Checktrigger_stages(PTE_ALIENS, g_alienStage.integer);
  //    if (g_alienStage.integer == S2)
  //      level.alienStage2Time = level.time;
  //    else if (g_alienStage.integer == S3)
  //      level.alienStage3Time = level.time;
  //
  //    lastAlienStageModCount = g_alienStage.modificationCount;
  //  }
  //
  //  if (g_humanStage.modificationCount > lastHumanStageModCount)
  //  {
  //    G_Checktrigger_stages(PTE_HUMANS, g_humanStage.integer);
  //
  //    if (g_humanStage.integer == S2)
  //      level.humanStage2Time = level.time;
  //    else if (g_humanStage.integer == S3)
  //      level.humanStage3Time = level.time;
  //
  //    lastHumanStageModCount = g_humanStage.modificationCount;
  //  }
}

/*
 ============
 CalculateAvgPlayers

 Calculates the average number of players playing this game
 ============
 */
void
G_CalculateAvgPlayers(void)
{
  //there are no clients or only spectators connected, so
  //reset the number of samples in order to avoid the situation
  //where the average tends to 0
  if (!level.numAlienClients)
  {
    level.numAlienSamples = 0;
    trap_Cvar_Set("g_alienKills", "0");
  }

  if (!level.numHumanClients)
  {
    level.numHumanSamples = 0;
    trap_Cvar_Set("g_humanKills", "0");
  }

  //calculate average number of clients for stats
  level.averageNumAlienClients = ((level.averageNumAlienClients * level.numAlienSamples)
      + level.numAlienClients) / (float) (level.numAlienSamples + 1);
  level.numAlienSamples++;

  level.averageNumHumanClients = ((level.averageNumHumanClients * level.numHumanSamples)
      + level.numHumanClients) / (float) (level.numHumanSamples + 1);
  level.numHumanSamples++;
}

/*
 ============
 CalculateRanks

 Recalculates the score ranks of all players
 This will be called on every client connect, begin, disconnect, death,
 and team change
 ============
 */
void
CalculateRanks(void)
{
  int i;
  char P[MAX_CLIENTS + 1] =
  { "" };
  int ff = 0;

  level.numConnectedClients = 0;
  level.numNonSpectatorClients = 0;
  level.numPlayingClients = 0;
  level.numVotingClients = 0; // don't count bots
  level.numAlienClients = 0;
  level.numHumanClients = 0;
  level.numLiveAlienClients = 0;
  level.numLiveHumanClients = 0;

  for(i = 0;i < level.maxclients;i++)
  {
    P[i] = '-';
    if (level.clients[i].pers.connected != CON_DISCONNECTED)
    {
      level.sortedClients[level.numConnectedClients] = i;
      level.numConnectedClients++;
      P[i] = (char) '0' + level.clients[i].pers.teamSelection;

      if (level.clients[i].pers.connected != CON_CONNECTED)
        continue;

      level.numVotingClients++;
      if (level.clients[i].pers.teamSelection != PTE_NONE)
      {
        level.numPlayingClients++;
        if (level.clients[i].sess.sessionTeam != TEAM_SPECTATOR)
          level.numNonSpectatorClients++;

        if (level.clients[i].pers.teamSelection == PTE_ALIENS)
        {
          level.numAlienClients++;
          if (level.clients[i].sess.sessionTeam != TEAM_SPECTATOR)
            level.numLiveAlienClients++;
        }
        else if (level.clients[i].pers.teamSelection == PTE_HUMANS)
        {
          level.numHumanClients++;
          if (level.clients[i].sess.sessionTeam != TEAM_SPECTATOR)
            level.numLiveHumanClients++;
        }
      }
    }
  }
  level.numteamVotingClients[0] = level.numHumanClients;
  level.numteamVotingClients[1] = level.numAlienClients;
  P[i] = '\0';
  trap_Cvar_Set("P", P);

  if (g_friendlyFire.value > 0)
    ff |= (FFF_HUMANS | FFF_ALIENS);
  if (g_friendlyFireHumans.value > 0)
    ff |= FFF_HUMANS;
  if (g_friendlyFireAliens.value > 0)
    ff |= FFF_ALIENS;
  if (g_friendlyBuildableFire.value > 0)
    ff |= FFF_BUILDABLES;
  trap_Cvar_Set("ff", va("%i", ff));

  qsort(level.sortedClients, level.numConnectedClients, sizeof(level.sortedClients[0]), SortRanks);

  // see if it is time to end the level
  CheckExitRules();

  // if we are at the intermission, send the new info to everyone
  if (level.intermissiontime)
    SendScoreboardMessageToAllClients();
}

/*
 ========================================================================

 MAP CHANGING

 ========================================================================
 */

/*
 ========================
 SendScoreboardMessageToAllClients

 Do this at BeginIntermission time and whenever ranks are recalculated
 due to enters/exits/forced team changes
 ========================
 */
void
SendScoreboardMessageToAllClients(void)
{
  int i;

  for(i = level.botslots;i < level.botslots + level.numConnectedClients;i++)
  {
    if (level.clients[i].pers.connected == CON_CONNECTED)
      ScoreboardMessage(g_entities + i);
  }
}

/*
 ========================
 MoveClientToIntermission

 When the intermission starts, this will be called for all players.
 If a new client connects, this will be called after the spawn function.
 ========================
 */
void
MoveClientToIntermission(gentity_t *ent)
{
  // take out of follow mode if needed
  if (ent->client->sess.spectatorState == SPECTATOR_FOLLOW)
    G_StopFollowing(ent);

  // move to the spot
  VectorCopy(level.intermission_origin, ent->s.origin);
  VectorCopy(level.intermission_origin, ent->client->ps.origin);
  VectorCopy(level.intermission_angle, ent->client->ps.viewangles);
  ent->client->ps.pm_type = PM_INTERMISSION;

  // clean up powerup info
  memset(ent->client->ps.powerups, 0, sizeof(ent->client->ps.powerups));

  ent->client->ps.eFlags = 0;
  ent->s.eFlags = 0;
  ent->s.eType = ET_GENERAL;
  ent->s.modelindex = 0;
  ent->s.loopSound = 0;
  ent->s.event = 0;
  ent->r.contents = 0;
}

/*
 ==================
 FindIntermissionPoint

 This is also used for spectator spawns
 ==================
 */
void
FindIntermissionPoint(void)
{
  gentity_t *ent, *target;
  vec3_t dir;

  // find the intermission spot
  ent = G_Find(NULL, FOFS(classname), "info_player_intermission");

  if (!ent)
  { // the map creator forgot to put in an intermission point...
    G_SelectSpawnPoint(vec3_origin, level.intermission_origin, level.intermission_angle);
  }
  else
  {
    VectorCopy(ent->s.origin, level.intermission_origin);
    VectorCopy(ent->s.angles, level.intermission_angle);
    // if it has a target, look towards it
    if (ent->target)
    {
      target = G_PickTarget(ent->target);

      if (target)
      {
        VectorSubtract(target->s.origin, level.intermission_origin, dir);
        vectoangles(dir, level.intermission_angle);
      }
    }
  }

}

/*
 ==================
 BeginIntermission
 ==================
 */
void
BeginIntermission(void)
{
  int i;
  gentity_t *client;

  if (level.intermissiontime)
    return; // already active

  level.numTeamWarnings = 99;

  level.intermissiontime = level.time;

  G_ClearVotes();

  FindIntermissionPoint();

  // move all clients to the intermission point
  for(i = 0;i < level.maxclients;i++)
  {
    client = g_entities + i;

    if (!client->inuse)
      continue;

    // respawn if dead
    if (client->health <= 0)
      respawn(client);

    MoveClientToIntermission(client);
  }

  // send the current scoring to all clients
  SendScoreboardMessageToAllClients();
}

/*
 =============
 ExitLevel

 When the intermission has been exited, the server is either moved
 to a new map based on the map rotation or the current map restarted
 =============
 */
void
ExitLevel(void)
{
  int i;
  gclient_t *cl;
  buildHistory_t *tmp, *mark;

  while((tmp = level.buildHistory))
  {
    level.buildHistory = level.buildHistory->next;
    while((mark = tmp))
    {
      tmp = tmp->marked;
      G_Free(mark);
    }
  }

  if (G_MapExists(g_nextMap.string))
    trap_SendConsoleCommand(EXEC_APPEND, va("!map %s\n", g_nextMap.string));
  else if (G_MapRotationActive())
    G_AdvanceMapRotation();
  else
    trap_SendConsoleCommand(EXEC_APPEND, "map_restart\n");

  trap_Cvar_Set("g_nextMap", "");

  level.restarted = qtrue;
  level.changemap = NULL;
  level.intermissiontime = 0;

  // reset all the scores so we don't enter the intermission again
  for(i = 0;i < g_maxclients.integer;i++)
  {
    cl = level.clients + i;
    if (cl->pers.connected != CON_CONNECTED)
      continue;

    cl->ps.persistant[PERS_SCORE] = 0;
  }

  // we need to do this here before chaning to CON_CONNECTING
  G_WriteSessionData();

  // change all client states to connecting, so the early players into the
  // next level will know the others aren't done reconnecting
  for(i = 0;i < g_maxclients.integer;i++)
  {
    if (level.clients[i].pers.connected == CON_CONNECTED)
      level.clients[i].pers.connected = CON_CONNECTING;
  }

}

/*
 =================
 G_AdminsPrintf

 Print to all active admins, and the logfile with a time stamp if it is open, and to the console
 =================
 */
void QDECL
G_AdminsPrintf(const char *fmt, ...)
{
  va_list argptr;
  char string[1024];
  gentity_t *tempent;
  int j;

  va_start(argptr, fmt);
  vsprintf(string, fmt, argptr);
  va_end(argptr);

  for(j = 0;j < level.maxclients;j++)
  {
    tempent = &g_entities[j];
    if (G_admin_permission(tempent, ADMF_ADMINCHAT))
    {
      trap_SendServerCommand(tempent - g_entities, va("print \"^6[Admins]^7 %s\"", string));
    }
  }

  G_LogPrintf("%s", string);

}

/*
 =================
 G_LogPrintf

 Print to the logfile with a time stamp if it is open
 =================
 */
void QDECL
G_LogPrintf(const char *fmt, ...)
{
  va_list argptr;
  char string[1024], decoloured[1024];
  int min, tens, sec;

  sec = (level.time - level.startTime) / 1000;

  min = sec / 60;
  sec -= min * 60;
  tens = sec / 10;
  sec -= tens * 10;

  Com_sprintf(string, sizeof(string), "%3i:%i%i ", min, tens, sec);

  va_start(argptr, fmt);
  vsprintf(string + 7, fmt, argptr);
  va_end(argptr);

  if (g_dedicated.integer)
    G_Printf("%s", string + 7);

  if (!level.logFile)
    return;

  if (g_decolourLogfiles.integer)
  {
    G_DecolorString(string, decoloured);
    trap_FS_Write(decoloured, strlen(decoloured), level.logFile);
  }
  else
  {
    trap_FS_Write(string, strlen(string), level.logFile);
  }
}

/*
 =================
 G_LogPrintfColoured

 Bypasses g_decolourLogfiles for events that need colors in the logs
 =================
 */
void QDECL
G_LogPrintfColoured(const char *fmt, ...)
{
  va_list argptr;
  char string[1024];
  int min, tens, sec;

  sec = (level.time - level.startTime) / 1000;

  min = sec / 60;
  sec -= min * 60;
  tens = sec / 10;
  sec -= tens * 10;

  Com_sprintf(string, sizeof(string), "%3i:%i%i ", min, tens, sec);

  va_start(argptr, fmt);
  vsprintf(string + 7, fmt, argptr);
  va_end(argptr);

  if (g_dedicated.integer)
    G_Printf("%s", string + 7);

  if (!level.logFile)
    return;

  trap_FS_Write(string, strlen(string), level.logFile);
}

/*
 =================
 G_LogOnlyPrintf

 Print to the logfile only (not console) with a time stamp if it is open
 =================
 */
void QDECL
G_LogOnlyPrintf(const char *fmt, ...)
{
  va_list argptr;
  char string[1024], decoloured[1024];
  int min, tens, sec;

  sec = (level.time - level.startTime) / 1000;

  min = sec / 60;
  sec -= min * 60;
  tens = sec / 10;
  sec -= tens * 10;

  Com_sprintf(string, sizeof(string), "%3i:%i%i ", min, tens, sec);

  va_start(argptr, fmt);
  vsprintf(string + 7, fmt, argptr);
  va_end(argptr);

  if (!level.logFile)
    return;

  if (g_decolourLogfiles.integer)
  {
    G_DecolorString(string, decoloured);
    trap_FS_Write(decoloured, strlen(decoloured), level.logFile);
  }
  else
  {
    trap_FS_Write(string, strlen(string), level.logFile);
  }
}

/*
 =================
 G_SendGameStat
 =================
 */
void
G_SendGameStat(pTeam_t team)
{
  char map[MAX_STRING_CHARS];
  char teamChar;
  char data[BIG_INFO_STRING];
  char entry[MAX_STRING_CHARS];
  int i, dataLength, entryLength;
  gclient_t *cl;

  trap_Cvar_VariableStringBuffer("mapname", map, sizeof(map));

  switch(team)
  {
    case PTE_ALIENS:
      teamChar = 'A';
      break;
    case PTE_HUMANS:
      teamChar = 'H';
      break;
    case PTE_NONE:
      teamChar = 'L';
      break;
    default:
      return;
  }

  Com_sprintf(
    data, BIG_INFO_STRING,
    "%s %s T:%c A:%f H:%f M:%s D:%d SD:%d AS:%d AS2T:%d AS3T:%d HS:%d HS2T:%d HS3T:%d CL:%d",
    Q3_VERSION, g_tag.string, teamChar, level.averageNumAlienClients, level.averageNumHumanClients,
    map, level.time - level.startTime, G_TimeTilSuddenDeath(), g_alienStage.integer,
    level.alienStage2Time - level.startTime, level.alienStage3Time - level.startTime,
    g_humanStage.integer, level.humanStage2Time - level.startTime, level.humanStage3Time
        - level.startTime, level.numConnectedClients);

  dataLength = strlen(data);

  for(i = 0;i < level.numConnectedClients;i++)
  {
    int ping;

    cl = &level.clients[level.sortedClients[i]];

    if (cl->pers.connected == CON_CONNECTING)
      ping = -1;
    else
      ping = cl->ps.ping < 999 ? cl->ps.ping : 999;

    switch(cl->ps.stats[STAT_PTEAM])
    {
      case PTE_ALIENS:
        teamChar = 'A';
        break;
      case PTE_HUMANS:
        teamChar = 'H';
        break;
      case PTE_NONE:
        teamChar = 'S';
        break;
      default:
        return;
    }

    Com_sprintf(
      entry, MAX_STRING_CHARS, " \"%s\" %c %d %d %d", cl->pers.netname, teamChar,
      cl->ps.persistant[PERS_SCORE], ping, (level.time - cl->pers.enterTime) / 60000);

    entryLength = strlen(entry);

    if (dataLength + entryLength >= BIG_INFO_STRING)
      break;

    strcpy(data + dataLength, entry);
    dataLength += entryLength;
  }

  trap_SendGameStat(data);
}

/*
 ================
 LogExit

 Append information about this game to the log file
 ================
 */
void
LogExit(const char *string)
{
  int i, numSorted;
  gclient_t *cl;
  gentity_t *ent;

  G_LogPrintf("Exit: %s\n", string);

  level.intermissionQueued = level.time;

  // this will keep the clients from playing any voice sounds
  // that will get cut off when the queued intermission starts
  trap_SetConfigstring(CS_INTERMISSION, "1");

  // don't send more than 32 scores (FIXME?)
  numSorted = level.numConnectedClients;
  if (numSorted > 32)
    numSorted = 32;

  for(i = 0;i < numSorted;i++)
  {
    int ping;

    cl = &level.clients[level.sortedClients[i]];

    if (cl->ps.stats[STAT_PTEAM] == PTE_NONE)
      continue;

    if (cl->pers.connected == CON_CONNECTING)
      continue;

    ping = cl->ps.ping < 999 ? cl->ps.ping : 999;

    G_LogPrintf(
      "score: %i  ping: %i  client: %i %s\n", cl->ps.persistant[PERS_SCORE], ping,
      level.sortedClients[i], cl->pers.netname);

  }

  for(i = 1, ent = g_entities + i;i < level.num_entities;i++, ent++)
  {
    if (!ent->inuse)
      continue;

    if (!Q_stricmp(ent->classname, "trigger_win"))
    {
      if (level.lastWin == ent->stageTeam)
        ent->use(ent, ent, ent);
    }
  }

  G_SendGameStat(level.lastWin);
}

/*
 =================
 CheckIntermissionExit

 The level will stay at the intermission for a minimum of 5 seconds
 If all players wish to continue, the level will then exit.
 If one or more players have not acknowledged the continue, the game will
 wait 10 seconds before going on.
 =================
 */
void
CheckIntermissionExit(void)
{
  int ready, notReady, numPlayers;
  int i;
  gclient_t *cl;
  int readyMask;

  //if no clients are connected, just exit
  if (!level.numConnectedClients)
  {
    ExitLevel();
    return;
  }

  // see which players are ready
  ready = 0;
  notReady = 0;
  readyMask = 0;
  numPlayers = 0;
  for(i = 0;i < g_maxclients.integer;i++)
  {
    cl = level.clients + i;
    if (cl->pers.connected != CON_CONNECTED)
      continue;

    if (cl->ps.stats[STAT_PTEAM] == PTE_NONE)
      continue;

    if (cl->readyToExit)
    {
      ready++;
      if (i < 16)
        readyMask |= 1 << i;
    }
    else
      notReady++;

    numPlayers++;
  }

  trap_SetConfigstring(CS_CLIENTS_READY, va("%d", readyMask));

  // never exit in less than five seconds
  if (level.time < level.intermissiontime + 5000)
    return;

  // never let intermission go on for over 20 seconds.
  if (level.time > level.intermissiontime + 20000)
  {
    ExitLevel();
    return;
  }

  // if nobody wants to go, clear timer
  if (!ready && numPlayers)
  {
    level.readyToExit = qfalse;
    return;
  }

  // if everyone wants to go, go now
  if (!notReady)
  {
    ExitLevel();
    return;
  }

  // the first person to ready starts the thirty second timeout
  if (!level.readyToExit)
  {
    level.readyToExit = qtrue;
    level.exitTime = level.time;
  }

  // if we have waited thirty seconds since at least one player
  // wanted to exit, go ahead
  if (level.time < level.exitTime + 30000)
    return;

  ExitLevel();
}

/*
 =============
 ScoreIsTied
 =============
 */
qboolean
ScoreIsTied(void)
{
  int a, b;

  if (level.numPlayingClients < 2)
    return qfalse;

  a = level.clients[level.sortedClients[0]].ps.persistant[PERS_SCORE];
  b = level.clients[level.sortedClients[1]].ps.persistant[PERS_SCORE];

  return a == b;
}

/*
 =================
 CheckExitRules

 There will be a delay between the time the exit is qualified for
 and the time everyone is moved to the intermission spot, so you
 can see the last frag.
 =================
 */
void
CheckExitRules(void)
{
  // if at the intermission, wait for all non-bots to
  // signal ready, then go to next level
  if (level.intermissiontime)
  {
    CheckIntermissionExit();
    return;
  }

  if (level.intermissionQueued)
  {
    if (level.time - level.intermissionQueued >= INTERMISSION_DELAY_TIME)
    {
      level.intermissionQueued = 0;
      BeginIntermission();
    }

    return;
  }

  if (g_timelimit.integer)
  {
    if (level.time - level.startTime >= g_timelimit.integer * 60000)
    {
      level.lastWin = PTE_NONE;
      trap_SendServerCommand(-1, "print \"Timelimit hit\n\"");
      trap_SetConfigstring(CS_WINNER, "Stalemate");
      LogExit("Timelimit hit.");
      G_admin_maplog_result("t");
      return;
    }
    else if (level.time - level.startTime >= (g_timelimit.integer - 5) * 60000
        && level.timelimitWarning < TW_IMMINENT)
    {
      trap_SendServerCommand(-1, "cp \"5 minutes remaining!\"");
      level.timelimitWarning = TW_IMMINENT;
    }
    else if (level.time - level.startTime >= (g_timelimit.integer - 1) * 60000
        && level.timelimitWarning < TW_PASSED)
    {
      trap_SendServerCommand(-1, "cp \"1 minute remaining!\"");
      level.timelimitWarning = TW_PASSED;
    }
  }

  if (level.uncondHumanWin || ((level.time > level.startTime + 1000) && (level.numAlienSpawns == 0)
      && (level.numLiveAlienClients == 0)))
  {
    //humans win
    level.lastWin = PTE_HUMANS;
    trap_SendServerCommand(-1, "print \"Good Job!\n\"");
    trap_SetConfigstring(CS_WINNER, "Humans Win");
    LogExit("Humans win.");
    G_admin_maplog_result("h");
  }
  else if (level.uncondAlienWin || ((level.time > level.startTime + 1000) && (level.numHumanSpawns
      == 0) && (level.numLiveHumanClients == 0)))
  {
    //aliens win
    level.lastWin = PTE_ALIENS;
    if (g_survival.integer)
    {
      if (level.survivalRecordsBroke[0] > 0)
      {
        level.survivalRecordTime = level.time - level.startTime;
        trap_SendServerCommand(-1, "print \"New Record.\n\"");
      }
      else
      {
        trap_SendServerCommand(-1, "print \"Good Luck next time.\n\"");
      }
    }
    else
    {
      trap_SendServerCommand(-1, "print \"Tip: Teamwork.\n\"");
    }
    trap_SetConfigstring(CS_WINNER, "Aliens Win");
    LogExit("Aliens win.");
    G_admin_maplog_result("a");
  }
}

/*
 ========================================================================

 FUNCTIONS CALLED EVERY FRAME

 ========================================================================
 */

/*
 ==================
 CheckVote
 ==================
 */
void
CheckVote(void)
{
  int votePassThreshold = level.votePassThreshold;
  int voteYesPercent;

  if (level.voteExecuteTime && level.voteExecuteTime < level.time)
  {
    level.voteExecuteTime = 0;

    if (!Q_stricmp(level.voteString, "map_restart"))
    {
      G_admin_maplog_result("r");
    }
    else if (!Q_stricmpn(level.voteString, "map", 3))
    {
      G_admin_maplog_result("m");
    }

    if (!Q_stricmp(level.voteString, "suddendeath"))
    {
      level.suddenDeathBeginTime = level.time + (1000 * g_suddenDeathVoteDelay.integer)
          - level.startTime;

      level.voteString[0] = '\0';

      if (g_suddenDeathVoteDelay.integer)
        trap_SendServerCommand(-1, va(
          "cp \"Sudden Death will begin in %d seconds\n\"", g_suddenDeathVoteDelay.integer));
    }

    if (level.voteString[0])
      trap_SendConsoleCommand(EXEC_APPEND, va("%s\n", level.voteString));

    if (!Q_stricmp(level.voteString, "map_restart") || !Q_stricmpn(level.voteString, "map", 3))
    {
      level.restarted = qtrue;
    }
  }

  if (!level.voteTime)
    return;

  if (level.voteYes + level.voteNo > 0)
    voteYesPercent = (int) (100 * (level.voteYes) / (level.voteYes + level.voteNo));
  else
    voteYesPercent = 0;

  if ((level.time - level.voteTime >= VOTE_TIME) || (level.voteYes + level.voteNo
      == level.numConnectedClients))
  {
    if (voteYesPercent > votePassThreshold || level.voteNo == 0)
    {
      // execute the command, then remove the vote
      trap_SendServerCommand(-1, va(
        "print \"Vote passed (%d - %d)\n\"", level.voteYes, level.voteNo));
      G_LogPrintf("Vote: Vote passed (%d-%d)\n", level.voteYes, level.voteNo);
      level.voteExecuteTime = level.time + 3000;
    }
    else
    {
      // same behavior as a timeout
      trap_SendServerCommand(-1, va(
        "print \"Vote failed (%d - %d)\n\"", level.voteYes, level.voteNo));
      G_LogPrintf("Vote: Vote failed (%d - %d)\n", level.voteYes, level.voteNo);
    }
  }
  else
  {
    if (level.voteYes > (int) ((double) level.numConnectedClients * ((double) votePassThreshold
        / 100.0)))
    {
      // execute the command, then remove the vote
      trap_SendServerCommand(-1, va(
        "print \"Vote passed (%d - %d)\n\"", level.voteYes, level.voteNo));
      G_LogPrintf("Vote: Vote passed (%d - %d)\n", level.voteYes, level.voteNo);
      level.voteExecuteTime = level.time + 3000;
    }
    else if (level.voteNo > (int) ((double) level.numConnectedClients * ((double) (100.0
        - votePassThreshold) / 100.0)))
    {
      // same behavior as a timeout
      trap_SendServerCommand(-1, va(
        "print \"Vote failed (%d - %d)\n\"", level.voteYes, level.voteNo));
      G_LogPrintf("Vote failed\n");
    }
    else
    {
      // still waiting for a majority
      return;
    }
  }

  level.voteTime = 0;
  trap_SetConfigstring(CS_VOTE_TIME, "");
  trap_SetConfigstring(CS_VOTE_STRING, "");
}

/*
 ==================
 CheckTeamVote
 ==================
 */
void
CheckTeamVote(int team)
{
  int cs_offset;

  if (team == PTE_HUMANS)
    cs_offset = 0;
  else if (team == PTE_ALIENS)
    cs_offset = 1;
  else
    return;

  if (!level.teamVoteTime[cs_offset])
    return;

  if (level.time - level.teamVoteTime[cs_offset] >= VOTE_TIME)
  {
    if (level.teamVoteYes[cs_offset] > level.teamVoteNo[cs_offset] && level.teamVoteYes[cs_offset]
        >= 2)
    {
      // execute the command, then remove the vote
      trap_SendServerCommand(-1, va(
        "print \"Team vote passed  (%d - %d)\n\"", level.teamVoteYes[cs_offset],
        level.teamVoteNo[cs_offset]));
      trap_SendConsoleCommand(EXEC_APPEND, va("%s\n", level.teamVoteString[cs_offset]));
    }
    else
    {
      trap_SendServerCommand(-1, va(
        "print \"Team vote failed  (%d - %d)\n\"", level.teamVoteYes[cs_offset],
        level.teamVoteNo[cs_offset]));
      G_LogPrintf(
        "Teamvote: Team vote failed (%d - %d)\n", level.teamVoteYes[cs_offset],
        level.teamVoteNo[cs_offset]);
    }
  }
  else
  {
    if (level.teamVoteYes[cs_offset] > level.numteamVotingClients[cs_offset] / 2)
    {
      // execute the command, then remove the vote
      trap_SendServerCommand(-1, va(
        "print \"Team vote passed  (%d - %d)\n\"", level.teamVoteYes[cs_offset],
        level.teamVoteNo[cs_offset]));
      G_LogPrintf(
        "Teamvote: Team vote passed (%d - %d)\n", level.teamVoteYes[cs_offset],
        level.teamVoteNo[cs_offset]);
      //
      trap_SendConsoleCommand(EXEC_APPEND, va("%s\n", level.teamVoteString[cs_offset]));
    }
    else if (level.teamVoteNo[cs_offset] >= level.numteamVotingClients[cs_offset] / 2)
    {
      // same behavior as a timeout
      trap_SendServerCommand(-1, va(
        "print \"Team vote failed  (%d - %d)\n\"", level.teamVoteYes[cs_offset],
        level.teamVoteNo[cs_offset]));
      G_LogPrintf(
        "Teamvote: Team vote failed (%d - %d)\n", level.teamVoteYes[cs_offset],
        level.teamVoteNo[cs_offset]);
    }
    else
    {
      // still waiting for a majority
      return;
    }
  }

  level.teamVoteTime[cs_offset] = 0;
  trap_SetConfigstring(CS_TEAMVOTE_TIME + cs_offset, "");
  trap_SetConfigstring(CS_TEAMVOTE_STRING + cs_offset, "");
}

/*
 ==================
 CheckMsgTimer
 ==================
 */
void
CheckMsgTimer(void)
{
  if (!g_msgTime.integer)
    return;

  if (level.time - level.lastMsgTime < abs(g_msgTime.integer) * 60000)
    return;

  // negative settings only print once per map
  if ((level.lastMsgTime) && g_msgTime.integer < 0)
    return;

  level.lastMsgTime = level.time;

  if (g_msg.string[0])
  {
    char buffer[MAX_STRING_CHARS];

    Q_strncpyz(buffer, g_msg.string, sizeof(buffer));
    G_ParseEscapedString(buffer);
    trap_SendServerCommand(-1, va("cp \"%s\"", buffer));
    trap_SendServerCommand(-1, va("print \"%s\n\"", buffer));
  }
}

/*
 ==================
 CheckCountdown
 ==================
 */
void
CheckCountdown(void)
{
  static int lastmsg = 0;
  int timeleft = g_warmup.integer - (level.time - level.startTime) / 1000;

  if (!g_doWarmup.integer || timeleft < 0)
    return;

  if (level.time - lastmsg < 1000)
    return;

  lastmsg = level.time;
  if (timeleft > 0)
    trap_SendServerCommand(-1, va(
      "cp \"^1Warmup Time:^7\n^%i----- ^7%i ^%i-----\"", timeleft % 7, timeleft, timeleft % 7));
  else if (timeleft == 0)
    trap_SendServerCommand(-1, "cp \"^2----- GO! -----^7\"");
}

/*
 ==================
 CheckCvars
 ==================
 */
void
CheckCvars(void)
{
  static int lastPasswordModCount = -1;
  static int lastMarkDeconModCount = -1;
  static int lastSDTimeModCount = -1;

  if (g_password.modificationCount != lastPasswordModCount)
  {
    lastPasswordModCount = g_password.modificationCount;

    if (*g_password.string && Q_stricmp(g_password.string, "none"))
      trap_Cvar_Set("g_needpass", "1");
    else
      trap_Cvar_Set("g_needpass", "0");
  }

  // Unmark any structures for deconstruction when
  // the server setting is changed
  if (g_markDeconstruct.modificationCount != lastMarkDeconModCount)
  {
    int i;
    gentity_t *ent;

    lastMarkDeconModCount = g_markDeconstruct.modificationCount;

    for(i = 1, ent = g_entities + i;i < level.num_entities;i++, ent++)
    {
      if (!ent->inuse)
        continue;

      if (ent->s.eType != ET_BUILDABLE)
        continue;

      ent->deconstruct = qfalse;
    }
  }

  if (g_suddenDeathTime.modificationCount != lastSDTimeModCount)
  {
    lastSDTimeModCount = g_suddenDeathTime.modificationCount;
    level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000;
  }

  level.frameMsec = trap_Milliseconds();
}

/*
 =============
 G_RunThink

 Runs thinking code for this frame if necessary
 =============
 */
void
G_RunThink(gentity_t *ent)
{
  float thinktime;

  thinktime = ent->nextthink;
  if (thinktime <= 0)
    return;

  if (thinktime > level.time)
    return;

  ent->nextthink = 0;
  if (!ent->think)
    G_Error("NULL ent->think");

  ent->think(ent);
}

/*
 =============
 G_EvaluateAcceleration

 Calculates the acceleration for an entity
 =============
 */
void
G_EvaluateAcceleration(gentity_t *ent, int msec)
{
  vec3_t deltaVelocity;
  vec3_t deltaAccel;

  VectorSubtract(ent->s.pos.trDelta, ent->oldVelocity, deltaVelocity);
  VectorScale(deltaVelocity, 1.0f / (float) msec, ent->acceleration);

  VectorSubtract(ent->acceleration, ent->oldAccel, deltaAccel);
  VectorScale(deltaAccel, 1.0f / (float) msec, ent->jerk);

  VectorCopy(ent->s.pos.trDelta, ent->oldVelocity);
  VectorCopy(ent->acceleration, ent->oldAccel);
}

/*
 ================
 G_RunFrame

 Advances the non-player objects in the world
 ================
 */
void
G_RunFrame(int levelTime)
{
  int i;
  gentity_t *ent, *ent2;
  int msec;
  int start, end;

  // if we are waiting for the level to restart, do nothing
  if (level.restarted)
    return;

  if (level.paused)
  {
    level.pausedTime = levelTime - level.time;
    if ((level.pausedTime % 3000) == 0)
      trap_SendServerCommand(-1, "cp \"The game has been paused. Please wait.\"");

    for(i = 0;i < level.maxclients;i++)
    {
      level.clients[i].ps.commandTime = levelTime;
    }

    return;
  }

  CheckMsgTimer();
  CheckCountdown();

  level.framenum++;
  level.previousTime = level.time;
  level.time = levelTime;
  msec = level.time - level.previousTime;

  //TA: seed the rng
  srand(level.framenum);

  // get any cvar changes
  G_UpdateCvars();

  //
  // go through all allocated objects
  //
  start = trap_Milliseconds();
  ent = &g_entities[0];

  for(i = 0;i < level.num_entities;i++, ent++)
  {
    if (!ent->inuse)
      continue;

    // clear events that are too old
    if (level.time - ent->eventTime > EVENT_VALID_MSEC)
    {
      if (ent->s.event)
      {
        ent->s.event = 0; // &= EV_EVENT_BITS;
        if (ent->client)
        {
          ent->client->ps.externalEvent = 0;
          //ent->client->ps.events[0] = 0;
          //ent->client->ps.events[1] = 0;
        }
      }

      if (ent->freeAfterEvent)
      {
        // tempEntities or dropped items completely go away after their event
        G_FreeEntity(ent);
        continue;
      }
      else if (ent->unlinkAfterEvent)
      {
        // items that will respawn will hide themselves after their pickup event
        ent->unlinkAfterEvent = qfalse;
        trap_UnlinkEntity(ent);
      }
    }

    // temporary entities don't think
    if (ent->freeAfterEvent)
      continue;

    //TA: calculate the acceleration of this entity
    if (ent->evaluateAcceleration)
      G_EvaluateAcceleration(ent, msec);

    if (!ent->r.linked && ent->neverFree)
      continue;

    if (ent->s.eType == ET_MISSILE)
    {
      G_RunMissile(ent);
      continue;
    }

    if (ent->s.eType == ET_BUILDABLE)
    {
      G_BuildableThink(ent, msec);
      continue;
    }

    if (ent->s.eType == ET_CORPSE || ent->physicsObject)
    {
      G_Physics(ent, msec);
      continue;
    }

    if (ent->s.eType == ET_MOVER)
    {
      G_RunMover(ent);
      continue;
    }

    if (i < MAX_CLIENTS)
    {
      G_RunClient(ent);
      continue;
    }

    G_RunThink(ent);
  }
  end = trap_Milliseconds();

  start = trap_Milliseconds();

  // perform final fixups on the players
  ent = &g_entities[0];

  for(i = 0;i < level.maxclients;i++, ent++)
  {
    if (ent->inuse)
      ClientEndFrame(ent);
  }

  // save position information for all active clients
  G_UnlaggedStore();

  end = trap_Milliseconds();

  //TA:
  G_CountSpawns();
  G_CalculateBuildPoints();
  G_CalculateStages();
  if (!g_survival.integer)
  {
    G_SpawnClients(PTE_ALIENS);
  }
  G_SpawnClients(PTE_HUMANS);
  G_CalculateAvgPlayers();
  G_UpdateZaps(msec);

  // see if it is time to end the level
  CheckExitRules();

  // update to team status?
  CheckTeamStatus();

  // cancel vote if timed out
  CheckVote();

  // check team votes
  CheckTeamVote(PTE_HUMANS);
  CheckTeamVote(PTE_ALIENS);

  //G_UpdateCamper();
  G_CalculateSurvivalRecords();

  //FIXME: PRODUCTION
  G_addBot();
  G_Director();

  // for tracking changes
  CheckCvars();

  if (g_listEntity.integer)
  {
    for(i = 0;i < MAX_GENTITIES;i++)
      if(g_entities[i].classname)
        G_Printf("%4i: %s\n", i, g_entities[i].classname);

    trap_Cvar_Set("g_listEntity", "0");
  }

  level.pausedTime = 0;
}

